Visual studio 在使用未处理的异常对话框处理异常时中断

Visual studio breaks on exception that IS handled with unhandled exception dialog

我的代码调用当前未 运行ning 的 WCF 服务。所以我们应该期待 EndPointNotFoundException. The using statement tries to Close() the faulted connection which causes a CommunicationObjectFaultedException 被排除在外。此异常在 using 块周围的 try catch 块中捕获:

class Program
{
    static void Main()
    {
        try
        {
            using (ChannelFactory<IDummyService> unexistingSvc = new ChannelFactory<IDummyService>(new NetNamedPipeBinding(), "net.pipe://localhost/UnexistingService-" + Guid.NewGuid().ToString()))
            {
                using (IClientChannel chan = (unexistingSvc.CreateChannel() as IClientChannel))
                {
                    (chan as IDummyService)?.Echo("Hello");
                }
            }
        }
        catch (EndpointNotFoundException ex)
        {
            Console.WriteLine("Expected");
        }
        catch (CommunicationObjectFaultedException ex)
        {
            Console.WriteLine("Expected: caused by closing channel that has thrown EndPointNotFoundException");
        }
    }
}

请注意,服务端点使用新的 Guid,因此它永远不会有服务侦听。

IDummyService 是:

[ServiceContract]
interface IDummyService
{
    [OperationContract]
    string Echo(string e);
}

这会导致 Visual Studio 调试器 (Visual Studio Professional 2017 15.4.1) 中断并显示 "Exception Unhandled" 弹出窗口: Visual Studio 中断的异常是 System.ServiceModel.CommunicationObjectFaultedException 在代码中捕获的。

步进继续执行表明达到了catch(CommunicationObjectFaultedException ex)。使用 LinqPad 到 运行 演示也显示按预期捕获了异常。

我还尝试明确(两次)关闭通道而不是使用 using-block:

class Program
{
    static void Main()
    {
        try
        {
            using (ChannelFactory<IDummyService> unexistingSvc = new ChannelFactory<IDummyService>(new NetNamedPipeBinding(), "net.pipe://localhost/UnexistingService-" + Guid.NewGuid().ToString()))
            {
                IDummyService chan = null;
                try
                {
                    chan = unexistingSvc.CreateChannel();
                    chan.Echo("Hello");
                }
                catch (EndpointNotFoundException ex)
                {
                    Console.WriteLine($"Expected: {ex.Message}");
                }
                finally
                {
                    try
                    {
                        (chan as IClientChannel)?.Close();
                    }
                    catch (CommunicationObjectFaultedException ex)
                    {
                        Console.WriteLine($"Caused by Close: {ex.Message}");
                    }
                }
            }
        }
        catch (EndpointNotFoundException ex)
        {
            Console.WriteLine("Expected");
        }
        catch (CommunicationObjectFaultedException ex)
        {
            Console.WriteLine("Expected: caused by closing channel that has thrown EndPointNotFoundException");
        }
    }
}

这仍然会导致调试器在 Close 语句处中断。

我的例外设置 System.ServiceModel.CommunicationObjectFaultedException 未选中。 (选中时 Visual studio 按预期中断,并使用 "Exception Thrown" 对话框而不是 "Exception Unhandled" 对话框)。

当我启用 "Options"\"Debugging"\"General"\"Enable Just My Code" 时,调试器不会中断。但是,我有 async 方法,异常应该留在我的代码中,后来我在 awaiting Task 时捕获了异常。对于这些方法,我需要 "Enable Just My Code" 未选中;参见 Stop visual studio from breaking on exception in Tasks

禁用 "Using the New Exception Helper"(如 Jack Zhai-MSFT 所建议)Visual Studio 仍然中断并显示 该对话框提供了一些附加信息:

The exception is not caught before it crosses a managed/native boundary.

我怀疑 using 块可能引入了这个 managed/native 边界。

是什么原因导致调试器错误中断以及如何使调试器既不中断也不处理 CommunicationObjectFaultedExceptions 也不在以后的处理程序 async 异常上?

新的Exception特性是在VS2017中,我们可以在TOOLS->OPTION->Debugging->General下禁用debugging选项"Use the New Exception Helper",它可以提供给你旧的Exception消息,你可以访问它。

旧异常消息显示异常跨越 managed/native 边界:

在TOOLS->OPTION->Debugging->General下勾选"Break when Exceptions cross AppDomain or managed/native boundaries"。禁用 "Break when Exceptions cross AppDomain or managed/native boundaries" 以避免 Visual Studio 在 OP 的情况下中断(尽管要小心,因为这也会在异常跨越 AppDomain 或 managed/native 边界的其他情况下禁用中断)。

Close()-ing a Faulted IClientChannel causes a CommunicationObjectFaultedException:

public void Close(TimeSpan timeout)
{
    ...
    switch (originalState)
    {
        case CommunicationState.Created:
        case CommunicationState.Opening:
        case CommunicationState.Faulted:
            this.Abort();
            if (originalState == CommunicationState.Faulted)
            {
                throw TraceUtility.ThrowHelperError(this.CreateFaultedException(), Guid.Empty, this);
            }
            break;
        ...
    }
    ...
}

--(参见 CommunicationObject.Close(TimeSpan) line #299 in the .NET framework 4.7 reference source)。

using-块被翻译成try { ... } finally { Dispose(); }Dispose() calls Close() when the block is left. The proxy returned by CreateChannel() is implemented via RealProxy (src) and RemotingServices.CreateTransparentProxy()这些代理结合了托管和非托管代码,这可能会导致异常越界。

组合设置(在TOOLS->OPTIONS->Debugger->General):

  • ☑ 当异常跨越 AppDomain 或 managed/native 边界时中断
  • ☐ 仅启用我的代码

导致 Visual Studio 中断显示:新的非模态异常弹出窗口 "Exception Unhandled": 或模态对话框:

CommunicationObjectFaultedException开始于'Not My Code';它跨越 managed/unmanaged 或 AppDomain 边界,同时仍在 'Not My Code' 中;最后进入 'My Code' 由 catch 块处理(但 Visual Studio 此时已经停止执行)。
由于异常从 'Not My Code' 开始并在跨越边界时保持在那里,因此选择选项 "Enable Just My Code" 会导致 Visual studio 即使在跨越 AppDomain 或 managed/unmanaged 时也不会中断异常边界.
取消选择 "break when exceptions cross AppDomain or managed/native boundaries" 也会导致 Visual Studio 不中断异常。

这给出了两个 solutions/workarounds

  • 选择 "Break when Exceptions cross AppDomain or managed/native boundaries""Enable Just My Code"
  • 的不同组合
  • 不要IClientChannel and check IClientChannel.State before Close()-ing. As is done in What is the best workaround for the WCF client `using` block issue?
  • 使用using