IErrorHandler.WCF的HandleError(异常错误)方法得到意外的TimoutException

本文关键字:方法 意外 TimoutException 错误 WCF HandleError 异常 IErrorHandler | 更新日期: 2023-09-27 18:11:17

我的目的是检测WCF服务中未处理的错误,记录它们并关闭应用程序。

为此,我使用WCF的IErrorHandler。在方法HandleError(Exception error)中,我被通知发生了异常。一切正常。在问题的最后,你会发现完整的清单。下面是输出:

00000: Starting service ...
00041: Client call ThrowUnexpected
00056: Service is throwing [InvalidOperationException]
00063: Client chatched [FaultException]
10070: ErrorHandler got [TimeoutException]
10070: ErrorHandler got [InvalidOperationException]

有两件事让我不高兴:

  1. 不是预期的InvalidOperationException,我首先得到TimeoutException,然后是我扔的那个。如果我在第一次登录后关机,我的日志中会有错误的信息。

  2. 回调不会立即到达,大约需要10秒。这些似乎正是net.tcp默认的超时秒数。这对我来说太晚了,因为我不想在意外发生后立即终止进程。

问题1:这是一个bug还是正常的,我得到我的异常只在第二名?我可以假设,对于任何WCF配置,我将得到这对异常吗?是否有任何方法只获得在方法内部抛出的异常?

问题2:是否有任何方法可以立即调用而不是在超时后调用?

清单:

    internal class Program
    {
        private static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            Console.WriteLine("{0:00000}: Starting service ...", stopwatch.ElapsedMilliseconds);
            var instance = new SomeService(stopwatch);
            var uri = new UriBuilder(Uri.UriSchemeNetTcp, IPAddress.Loopback.ToString(), 8085, "SomeService").Uri;
            using (var host = new ServiceHost(instance))
            {
                host.AddServiceEndpoint(typeof (ISomeService), new NetTcpBinding(), uri);
                host.Description.Behaviors.Add(new ErrorHandlerBehavior(new ErrorHandler(stopwatch)));
                host.Open();
                // DO NOT DISPOSE Channel is broken
                var proxy = new SomeServiceProxy(uri);
                {
                    try
                    {
                        Console.WriteLine("{0:00000}: Client call ThrowUnexpected", stopwatch.ElapsedMilliseconds);
                        proxy.ThrowUnexpected();
                    }
                    catch (FaultException ex)
                    {
                        Console.WriteLine("{0:00000}: Client chatched [{1}]", stopwatch.ElapsedMilliseconds,
                            ex.GetType().Name);
                    }
                }
            }
        }
    }
}
[ServiceContract]
public interface ISomeService
{
    [OperationContract]
    void ThrowUnexpected();
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class SomeService : ISomeService
{
    private readonly Stopwatch _stopwatch;
    public SomeService(Stopwatch stopwatch)
    {
        _stopwatch = stopwatch;
    }
    public void ThrowUnexpected()
    {
        var exception = new InvalidOperationException();
        Console.WriteLine("{0:00000}: Service is throwing [{1}]", _stopwatch.ElapsedMilliseconds,
            exception.GetType().Name);
        throw exception;
    }
}

public class ErrorHandler : IErrorHandler
{
    private readonly Stopwatch _stopwatch;
    public ErrorHandler(Stopwatch stopwatch)
    {
        _stopwatch = stopwatch;
    }
    public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
    {
    }
    public bool HandleError(Exception error)
    {
        Console.WriteLine("{0:00000}: ErrorHandler got [{1}]", _stopwatch.ElapsedMilliseconds, error.GetType().Name);
        return false;
    }
}
public class SomeServiceProxy : ClientBase<ISomeService>, ISomeService
{
    public SomeServiceProxy(Uri uri)
        : base(new NetTcpBinding(), new EndpointAddress(uri))
    {
    }
    public void ThrowUnexpected()
    {
        Channel.ThrowUnexpected();
    }
}

public class ErrorHandlerBehavior : IServiceBehavior
{
    private readonly IErrorHandler m_Handler;
    public ErrorHandlerBehavior(IErrorHandler errorHandler)
    {
        m_Handler = errorHandler;
    }
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }
    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
        Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
    }
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (var channelDispatcherBase in serviceHostBase.ChannelDispatchers)
        {
            var dispatcher = (ChannelDispatcher) channelDispatcherBase;
            dispatcher.ErrorHandlers.Add(m_Handler);
        }
    }
}

IErrorHandler.WCF的HandleError(异常错误)方法得到意外的TimoutException

我认为你对IErrorHandler的工作方式有一个小小的误解。我指的是MSDN。首先是ProvideFault方法

在发送响应消息之前,首先调用所有providedfault实现。当所有的ProvideFault实现都被调用并返回时,如果fault是非空的,则根据操作契约将其发送回客户端。如果在调用了所有实现之后fault为null,则响应消息由ServiceBehaviorAttribute控制。IncludeExceptionDetailInFaults属性值。

然后是HandleError方法

因为HandleError方法可以从许多不同的地方调用,所以不能保证该方法是在哪个线程上调用的。不要依赖在操作线程上调用的HandleError方法。

你看到的TimeoutException来自ServiceHost的关闭(using-Block的结束)。你可以通过在ServiceHost上设置CloseTimeout来控制它。

host.CloseTimeout = TimeSpan.FromSeconds(2);

为什么会发生超时?这是因为,从代理到服务的连接仍然存在并且没有关闭,即使代理处于故障状态。要解决这个问题,您需要在FaultedException的捕获块中调用Abort。

 catch (FaultException ex)
 {
   proxy.Abort();
   Console.WriteLine("{0:00000}: Client chatched [{1}]", stopwatch.ElapsedMilliseconds,
                            ex.GetType().Name);
 }

这将导致以下输出

00000: Starting service ...
00005: Client call ThrowUnexpected
00010: Service is throwing [InvalidOperationException]
00014: Client chatched [FaultException]
00026: ErrorHandler got [CommunicationException]
00029: ErrorHandler got [InvalidOperationException]