Where to trap failed connection on WCF calling class?

后端 未结 7 1137
失恋的感觉
失恋的感觉 2021-01-13 05:09

I\'m trying to write a class that encapsulates WCF calls (the client is Silverlight, if it matters). It all works swimmingly, but I\'m not sure how to trap a connection fail

相关标签:
7条回答
  • 2021-01-13 05:33

    I think that the problem is in the way you have designed service caller.

    The channel is opened when service caller is created. This means that the channel can timeout and there is nothing in your code that can recover from the timeount.

    I would move the creation and closing of the channel to the make call method.

    You can do a try catch around the begin get and around the contents of the callback.

    0 讨论(0)
  • 2021-01-13 05:34

    I've encountered a similar problem before. The main problem is with the way IDisposable is implemented on instances of your proxy/channel. The way I've solved it is shown in the code below, where IDirector is my service contract:

    public class ProxyWrapper : IDisposable
    {
        private IDirector proxy;
        private ChannelFactory<IDirector> factory;
        int callCount = 0;
    
        public ProxyWrapper()
        {
            factory = new ChannelFactory<IDirector>();
    
            proxy = factory.CreateChannel();
        }
    
        public IDirector Proxy
        {
            get
            {
                if (callCount > 0)
                    throw new InvalidOperationException("The proxy can only be accessed once for every ProxyWrapper instance. You must create a new ProxyWrapper instance for each service request.");
                // only allow the proxy/channel to be used for one call.
    
                callCount++;
                return proxy;
            }
        }
    
        public void Dispose()
        {
            IClientChannel channel = (IClientChannel)proxy;
    
            try
            {
                if (channel.State != CommunicationState.Faulted)
                {
                    channel.Close();
                }
                else
                {
                    channel.Abort();
                }
            }
            catch (CommunicationException)
            {
                channel.Abort();
            }
            catch (TimeoutException)
            {
                channel.Abort();
            }
            catch (Exception)
            {
                channel.Abort();
                throw;
            }
            finally
            {
                channel = null;
                proxy = null;
            }
        }
    }
    

    The way to use the above class is as follows:

        public static void Login(string userName, string password)
        {
            using (ProxyWrapper wrapper = new ProxyWrapper())
            {
                currentSession = wrapper.Proxy.Login(userName, password);
            }
        }
    

    Because the ProxyWrapper class implements IDisposable, if we use an instance of the ProxyWrapper class inside a using block, the Dispose() method is guaranteed to be called, even if an exception is thrown. The code in the Dispose() method will handle all cases and states of the proxy/channel. You can then add your error-handling / logging / event delegate code in this method.

    Read the following blog entry for more info, and for a more generic version of the code above: http://bloggingabout.net/blogs/erwyn/archive/2006/12/09/WCF-Service-Proxy-Helper.aspx

    0 讨论(0)
  • 2021-01-13 05:34

    Jeff, please show us your code when you try to put a try/catch block around the EndGet call. It's hard to believe that doesn't catch an exception, and seeing it will help me believe.

    Also, as part of this experiment, get rid of the UnhandledException event handler. I think you're catching this exception as soon as the BrowserHttpRequest realizes there's a 404. I think that without the UnhandledException event, a try/catch around EndGet will catch the exception, if any. I think you're picking up an intermediate state of the exception handling process.

    I'd also like to see you put a System.Debugger.Break or something immediately after the EndGet call.

    0 讨论(0)
  • 2021-01-13 05:36

    Am I right in thinking that you have designed your ServiceCaller class so that you can call it in a using block?

    If yes, then that's the problem. The WCF channel classes have been designed in such a way as to make it extremely difficult to write completely general disposing code that caters for all possible error conditions. If it were simple, then the channel classes themselves would be safe to dispose and you wouldn't need a wrapper class.

    The only safe way I have found to date is to encapsulate not the dispose logic, but the entire call sequence, thus:

    public static void Invoke<TContract>(ChannelFactory<TContract> factory, Action<TContract> action) where TContract : class {
    
        var proxy = (IClientChannel) factory.CreateChannel();
        bool success = false;
        try {
            action((TContract) proxy);
            proxy.Close();
            success = true;
        } finally {
            if(!success) {
                proxy.Abort();
            }
        }
    }
    

    I know that's a synchronous call, but the principle is the same. Instead of you trying to deduce whether you should call Close or Abort, you determine it in advance based on how far through the call you managed to get before it fell over.

    Note - only the actual channel needs this special treatment. You can dispose of the factory any way you like AFAIK.

    0 讨论(0)
  • 2021-01-13 05:40

    Your ChannelFactory instance is going to be in the "Aborted" state when this timeout occurs. You'll find that the exception is being thrown not when the exception happens in the call, but when you call _channel.Dispose().

    You'll need to guard against this exception somehow. The simplest way would be to put a try/catch around the contents of your dispose method, but it would be better if you checked the state of the _channel instance before trying to close/dispose.

    This is why you are still getting the exception - it's not being handled here.

    Update:

    Based on your stack trace, it looks like the framework is trying to execute your callback delegate and is getting an exception there. You responded elsewhere that try/catching the contents of your delegate doesn't solve the issue... what about inner exceptions? What is the full output of TheException.ToString()?

    I trolled through the .NET Framework and it is indeed allowed to travel all the way back to the threadpool (at least in the stacktrace you have here) which would cause an unhandled exception and your thread to die. If worse comes to worse, you can always take over the threading... spin up your own thread and inside call the service synchronously, rather than using the builtin async pattern (unless this is a WCF Duplex type of communication, which I don't think it is).

    I still think the full exception information (if there is anything more) might be useful.

    0 讨论(0)
  • 2021-01-13 05:41

    To catch communications errors with the server, I'd suggest putting the call to .CreateChannel() into a try...catch and handle things like CommunicationException and TimeoutExceptions there.

    Also, one nuggest of general advice: creating the ChannelFactory is quite an expensive operation, so you might want to do this separately and store that reference to the ChannelFactory object somewhere.

    If you need to create and possibly re-create the channel from the ChannelFactory, you can then use that stored / cached instance of the ChannelFactory over and over again without having to create that all the time.

    But other than that, I would think you're doing quite fine here!

    Marc

    0 讨论(0)
提交回复
热议问题