【问题标题】:WCF exception received on closing connection with callbacks in use关闭与正在使用的回调的连接时收到 WCF 异常
【发布时间】:2010-09-18 19:24:08
【问题描述】:

我正在使用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.

现在我明白了这个错误,但不幸的是,我不能保证在不再收到任何输入消息后关闭我的频道,因为用户可能随时关闭应用程序,因此工作仍将在 Windows 服务的后台继续进行(有点像病毒扫描程序的操作方式)。用户应该能够不受干扰地启动和关闭获胜管理工具应用程序。

现在错误,我在执行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();
        }
    }

还有我的 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;
            }
        }
    }

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

    [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
        }
        ...
    }

接口:

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

    [OperationContract(IsOneWay = true)]
    void Unsubscribe();
    ...
}

回调合约的接口,它并没有做任何令人兴奋的事情,只是通过委托等调用事件。我加入这个的原因是为了向你展示我的属性。我确实通过包含UseSynchronizationContext = false 缓解了一组死锁:

[CallbackBehavior(UseSynchronizationContext = false, 
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class TransferServiceCallback : ITransferServiceCallbackContract
{ ... }

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

【问题讨论】:

  • 我不知道具体问题,但对于信息,线程混乱听起来可能由于 WCF 如何使用同步上下文(这是通过 winforms 中的表单等)。
  • 感谢 Marc,是的,这让我感到困惑,我通过阅读该问题缓解了一组死锁,其中的诀窍是在回调合约上设置 UseSynchronizationContext = false ;) 我将把它添加到我的例子。
  • 啊,对;很高兴看到你已经涵盖了它;p
  • 是的。我非常讨厌处理 wcf 异常,并且完全不喜欢这个项目!正如你所看到的,根据我必须写的问题的大小,我必须讲述我的人生故事来解释它们:)

标签: c# .net wcf deadlock


【解决方案1】:

天哪,我发现了问题。

该异常与底层应用程序挂起无关,这只是一个您可以安全捕获的预防性异常。

你不会相信,我花了大约 6 个小时来解决这个 bug,结果是 channel.Close() 锁定等待挂起的 WCF 请求完成(在传输完成之前永远不会完成!这违背了目的!)

我只是一行一行地进行强力断点,我的问题是如果我太慢了.....它永远不会挂起,因为通道可以以某种方式关闭(甚至在传输完成之前)所以我不得不在 F5 下断点,然后快速踩到挂起,这就是它结束的那一行。我现在只需将超时值应用于Close() 操作并使用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;
        }
    }
}

我很高兴终于解决了这个错误!无论当前的 WCF 服务状态如何,我的应用程序现在在 3 秒超时后完全关闭,我希望我可以帮助那些发现自己遇到类似问题的人。

格雷厄姆

【讨论】:

  • 该异常与底层应用程序挂起无关,这只是您可以安全捕获的预防性异常。您是否有说明安全性的参考链接像这样吞下 ProtocolExceptions?我也有同样的问题。
猜你喜欢
  • 1970-01-01
  • 2011-06-30
  • 2010-09-18
  • 2023-02-05
  • 2010-10-27
  • 1970-01-01
  • 2014-04-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多