与正在使用的回调关闭连接时收到WCF异常

GON*_*ale 7 .net c# wcf deadlock

我使用a netNamedPipeBinding来执行从Windows应用程序到Windows服务的进程间WCF通信.

现在我的应用程序在所有其他帐户中运行良好(在与WCF异常的公平份额作斗争后,因为任何与WCF合作的人都会知道..)但是这个错误证明是非常有弹性的.

要绘制我的场景图片:我的Windows服务可以排队等待任何给定时间通过在Windows应用程序中按下的按钮进行一些工作,然后讨论netNamedPipeBinding哪个是一个支持回调(双向通信)的绑定你不熟悉并发起执行这项工作的请求(在这种情况下是一个文件上传程序)它还会每隔几秒抛出一次回调(事件),从文件进度到传输速度等等,回到windows应用程序,所以有一些相当紧密的客户端 - 服务器集成; 这就是我将我的Windows服务中运行的进度恢复到我的Windows应用程序的方式.

现在,一切都很好,WCF众神现在对我很满意,除了我每次关机时提前收到的一个令人讨厌的异常(这是一个完全有效的场景).虽然正在进行传输,并且回调频繁发生,但我收到此错误:

System.ServiceModel.ProtocolException:
  The channel received an unexpected input message with Action 
  'http://tempuri.org/ITransferServiceContract/TransferSpeedChangedCallback' 
  while closing. You should only close your channel when you are not expecting 
  any more input messages.
Run Code Online (Sandbox Code Playgroud)

现在我明白了这个错误,但遗憾的是我不能保证在从未收到任何更多的输入消息后关闭我的频道,因为用户可能随时关闭应用程序因此工作仍将继续在Windows服务的背景中(有点就像病毒扫描程序如何运作一样).用户应该能够在没有干扰的情况下尽可能多地启动和关闭win管理工具应用程序.

现在错误,我在执行我的Unsubscribe()呼叫后立即收到,这是终止应用程序之前的第二个最后一个呼叫,我认为这是断开WCF客户端的首选方式.在关闭连接之前,所有取消订阅都只是从本地存储在win服务wcf服务上的数组中删除客户端ID(因为这是win服务和Windows应用程序共享的实例,因为win服务可以在预定的事件本身)和我执行的客户端ID数组删除后,我希望(感觉)应该是一个干净的断开连接.

除了接收异常之外,其结果是我的应用程序挂起,UI处于完全锁定状态,进度条和中间的所有内容,所有迹象都指向具有竞争条件或WCF死锁[叹气],但我很漂亮线程精通现在,我认为这是一个相对孤立的情况,并按原样阅读异常,我不认为这是一个"线程"问题本身,因为它表明更多的早期断开连接的问题,然后螺旋所有我的线程陷入混乱,可能导致锁定.

Unsubscribe()客户端的方法如下所示:

    public void Unsubscribe()
    {
        try
        {
            // Close existing connections
            if (channel != null &&
                channel.State == CommunicationState.Opened)
            {
                proxy.Unsubscribe();
            }
        }
        catch (Exception)
        {
            // This is where we receive the 'System.ServiceModel.ProtocolException'.
        }
        finally
        {
            Dispose();
        }
    }
Run Code Online (Sandbox Code Playgroud)

我的Dispose()方法,应该执行干净的断开:

    public void Dispose()
    {
        // Dispose object
        if (channel != null)
        {
            try
            {
                // Close existing connections
                Close();
                // Attempt dispose object
                ((IDisposable)channel).Dispose();
            }
            catch (CommunicationException)
            {
                channel.Abort();
            }
            catch (TimeoutException)
            {
                channel.Abort();
            }
            catch (Exception)
            {
                channel.Abort();
                throw;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

并且Subscription()Windows服务服务器上的WCF服务对应和类属性(供参考)(这里没什么棘手的,我的异常发生在客户端):

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
    ConcurrencyMode = ConcurrencyMode.Multiple)] 
    public class TransferService : LoggableBase, ITransferServiceContract
    {
        public void Unsubscribe()
        {
            if (clients.ContainsKey(clientName))
            {
                lock (syncObj)
                {
                    clients.Remove(clientName);
                }
            }

#if DEBUG
            Console.WriteLine(" + {0} disconnected.", clientName);
#endif
        }
        ...
    }
Run Code Online (Sandbox Code Playgroud)

界面:

[ServiceContract(
    CallbackContract = typeof(ITransferServiceCallbackContract), 
    SessionMode = SessionMode.Required)]
public interface ITransferServiceContract
{
    [OperationContract(IsInitiating = true)]
    bool Subscribe();

    [OperationContract(IsOneWay = true)]
    void Unsubscribe();
    ...
}
Run Code Online (Sandbox Code Playgroud)

回调契约的界面,它没有做任何非常令人兴奋的事情,只是通过代表等来调用事件.我包含这个的原因是为了向你展示我的属性.我确实通过包括UseSynchronizationContext = false以下方式减轻了一组死锁:

[CallbackBehavior(UseSynchronizationContext = false, 
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class TransferServiceCallback : ITransferServiceCallbackContract
{ ... }
Run Code Online (Sandbox Code Playgroud)

真的希望有人可以帮助我!非常感谢= :)

GON*_*ale 12

哦,我的天哪,我发现了这个问题.

这个例外与underyling app hang无关,这只是一个你可以安全捕获的预防性例外.

你不会相信它,我花了大约6个小时打开和关闭这个bug,结果是channel.Close()等待挂起的WCF请求完成的锁定(在转移完成之前永远不会完成!这就失败了!)

我只是一行一行地进行了蛮力断点,我的问题是如果我太慢了.....它永远不会挂起,因为不知何故频道可以关闭(甚至在传输完成之前)所以我不得不断点F5,然后快速步骤抓住挂起,这就是它结束的线.我现在只是将一个超时值应用于Close()操作并用a捕获它TimeoutException然后如果它不能及时关闭则硬中止该通道!

请参阅修复代码:

private void Close()
{
    if (channel != null &&
        channel.State == CommunicationState.Opened)
    {
        // If cannot cleanly close down the app in 3 seconds,
        // channel is locked due to channel heavily in use
        // through callbacks or the like.
        // Throw TimeoutException
        channel.Close(new TimeSpan(0, 0, 0, 3));
    }
}

public void Dispose()
{
    // Dispose object
    if (channel != null)
    {
        try
        {
            // Close existing connections
            // *****************************
            // This is the close operation where we perform 
            //the channel close and timeout check and catch the exception.
            Close();

            // Attempt dispose object
            ((IDisposable)channel).Dispose();
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我很高兴终于完成了这个bug!无论当前的WCF服务状态如何,我的应用程序现在在3秒超时后干净地关闭,我希望我可以帮助其他发现自己遇到类似问题的人.

格雷厄姆