2011-05-27 141 views
2

我有一些实验代码,基本上只是试图做一个简单的场景工作。我有一个客户端正在将数据传输到多个服务。我遇到的问题是,如果其中一个服务没有正常关闭,我会得到一个我无法处理的EndpointNotFoundException。以下是我处理这个失败的尝试。实际上,我想从通道列表中删除失败的服务通道,并继续将流数据传输到仍处于运行状态的服务。定时器的东西只是在数据流开始之前给服务一个启动的机会。WCF F# - 处理服务在客户端上优雅地关闭

let prices = returns a seq of data that is streamed. 

type ReplayDataStream(prices) = 
    let evt = new Event<_>() 
    member x.Replay() = 
        async { for line, delay in prices do 
           do! Async.Sleep(delay) 
           evt.Trigger(line) } 
           |> Async.StartImmediate 

    member x.PriceChanged = evt.Publish 


let main() = 
    let addresses = new ResizeArray<EndpointAddress>() 

    let announcementService = new AnnouncementService() 

    let createChannels addresses = 
     let channels = new ResizeArray<IInputDataService>() 
     for (address:EndpointAddress) in addresses do 
       let channelFactory = new ChannelFactory<IInputDataService>(new BasicHttpBinding(), address) 
       let channel = channelFactory.CreateChannel() 
       (channel :?> ICommunicationObject).Faulted.Add(fun x -> 
                     (channel :?> ICommunicationObject).Abort() 
                     channels.Remove(channel) |> ignore 
                   ) 
       channels.Add(channel) 
     channels 

    let sendMessage(args:ElapsedEventArgs) = 
     let channels = createChannels addresses 
     for financialDataStream in prices do 
     let replayDataStreamA = new ReplayDataStream(financialDataStream) 
     for channel in channels do 
      try 
      //This is where it blows up and the try block isn't catching the exception. 
      replayDataStreamA.PriceChanged.Add(channel.InputStringData) 
      with 
      | :? EndpointNotFoundException as ex -> Console.WriteLine(ex.ToString()) 
      | :? CommunicationException as ex -> Console.WriteLine(ex.ToString()) 
      | :? Exception as ex -> Console.WriteLine(ex.ToString()) 
      replayDataStreamA.Replay() 

    let timer = new System.Timers.Timer() 
    timer.Enabled <- true 
    timer.AutoReset <- false 
    timer.Interval <- 30000.0 
    timer.Start() 
    timer.Elapsed.Add(sendMessage) 

    announcementService.OnlineAnnouncementReceived.Add(fun e -> 
                   Console.WriteLine(e.EndpointDiscoveryMetadata.Address) 
                   addresses.Add(e.EndpointDiscoveryMetadata.Address) 
                   ) 

    announcementService.OfflineAnnouncementReceived.Add(fun e -> 
                   Console.WriteLine(e.EndpointDiscoveryMetadata.Address) 
                   addresses.Remove(e.EndpointDiscoveryMetadata.Address) |> ignore 
                   ) 

    let announcementServiceHost = new ServiceHost(announcementService) 
    try 
     announcementServiceHost.AddServiceEndpoint(new UdpAnnouncementEndpoint()); 
     announcementServiceHost.Open(); 
    with 
    | :? System.ServiceModel.CommunicationException as ex -> Console.WriteLine(ex.ToString()) 
    | :? System.TimeoutException as ex -> Console.WriteLine(ex.ToString()) 


    printfn "%s" "Hit any key to close." 
    Console.ReadKey() |> ignore 
+0

http://stackoverflow.com/questions/2458631/wcf-c-unable-to-catch-endpointnotfoundexception/2672872#2672872 – GregC 2011-05-27 03:50:55

+0

这是我在上面的代码中试图实现的解决方案。我怀疑我可能没有正确地将它翻译成F#。 – Beaker 2011-05-27 04:03:50

回答

2

在C#中重写我的代码后,它终于明白我做错了什么。这是PriceChanged事件处理程序的外观。我需要捕捉lambda内部的异常。现在我需要写一些看起来像生产代码的东西。 :)

replayDataStreamA.PriceChanged.Add(fun x -> 
                  try 
                  channel.InputStringData x 
                  with 
                  | :? System.ServiceModel.CommunicationException as ex -> (channel :?> ICommunicationObject).Abort() 
                  ) 

为子孙后代这里是整个方法:

let sendMessage(args:ElapsedEventArgs) = 
      if(addresses.Count > 0) then 
       for address in addresses do 
        let channelFactory = new ChannelFactory<IInputDataService>(new BasicHttpBinding(), address) 
        let channel = channelFactory.CreateChannel() 
        for financialDataStream in prices do 
        let replayDataStreamA = new ReplayDataStream(financialDataStream) 
        replayDataStreamA.PriceChanged.Add(fun x -> 
                 try 
                 channel.InputStringData x 
                 with 
                 | :? System.ServiceModel.CommunicationException as ex -> (channel :?> ICommunicationObject).Abort() 
                 ) 
        replayDataStreamA.Replay() 
+0

非常感谢您的学习机会。 – GregC 2011-05-28 10:40:52

+0

@GregC - 感谢您的帮助。 – Beaker 2011-05-28 17:02:09

0

Sky Sanders的解释很有意义,应该适用于这种情况。这里有一个link to the blog.

为Faulted事件提供一个订阅者并不会像在异常处理程序中调用channel.Abort()那样完成同样的事情。

PriceChanged.Add()等价于PriceChanged + =:您正在为Price更改事件订阅处理程序。放置try/with块会捕获订阅时抛出的异常(想想你的事件中定制的添加/删除实现),这不是你想要的。您在调用InputStringData时正在寻找处理异常的方法。这种思维过程自然导致your solution

在C#生产代码中,将一个try/catch块放在引发异常事件的点的周围。通过重新抛出用户和Debug.Assert抛出的异常,警告开发人员应该处理所有异常。在你的代码中,这意味着一个try/with块在evt.Trigger()中发出警告并重新抛出。

您可以暴露异步块,而不是在声明时运行它。这应该为您提供更高级别的编排权限:在sendMessage中。有一个特殊的API来捕捉异常,在一个中心位置处理取消和超时,这真的值得looking into

+0

这基本上是我在上面的代码中试图实现的解决方案。也许我把它翻译成F#是不正确的? – Beaker 2011-05-27 03:57:31

+0

抛出的线条上面注明了“这是爆炸的地方”。 – Beaker 2011-05-27 04:13:52

+0

该代码有效,如果单个服务以不正当的方式关闭,它就无法恢复。也许你可以将它粘贴到一个fsi会话中,看看是否有东西出现在你身上。 – Beaker 2011-05-27 04:17:24