异步 WCF 服务超时

Posted

技术标签:

【中文标题】异步 WCF 服务超时【英文标题】:Asynchronous WCF service timing out 【发布时间】:2012-07-16 17:51:14 【问题描述】:

我们有一个异步 WCF 服务操作,它从我们系统的所有不同组件获取日志文件并将它们发送到客户端。如果其中一个组件无法正常工作,这可能需要一段时间,因此如果此功能不会超时,那就太好了,但它也不应该导致客户端挂起。

我对异步 WCF 服务的理解是,当客户端向服务器请求某些东西时,服务器会立即回复一条消息说:“我在做。继续做你自己的事情,我会通知你的当我完成时。”然后释放连接以供客户端发出其他请求,同时服务器启动一个新线程来完成其大部分工作。当服务器完成后,它会向客户端发送一条带有结果的消息。正因为如此,服务器和客户端之间的连接是免费的,无论服务器花费多长时间,连接都不应超时。这是正确的吗?

如果是这种情况,那么我们的服务就没有按预期工作。当我测试该服务时,只要不到一分钟,它就可以按预期工作。但是,如果我强制它花费更长的时间,客户端会抛出 TimeoutException。既然服务是异步的,不应该永远不超时吗?如果是这样,我错过了什么?

我们使用此页面作为指南编写了我们的异步服务: http://code.msdn.microsoft.com/windowsdesktop/How-to-Implement-a-WCF-2090bec8

这是我的代码。这是服务合同:

[ServiceContract(CallbackContract = typeof(IInformationServiceCallBack), SessionMode = SessionMode.Required)]
public interface IInformationService

    //snip...

    [OperationContract(AsyncPattern=true)]
    [FaultContract(typeof(LogFileFault))]
    IAsyncResult BeginGetLogFiles(LogFileRequest[] logfileRequests,
        AsyncCallback callback, object state);

    LogFile[] EndGetLogFiles(IAsyncResult result);

    //snip...

这是服务实现:

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerSession, UseSynchronizationContext=false)]
public class InformationServiceImpl : IInformationService, IDisposable

    //snip...

    public IAsyncResult BeginGetLogFiles(LogFileRequest[] logfileRequests,
        AsyncCallback callback, object state)
    
        var task = Task<LogFile[]>.Factory.StartNew((x) =>
            
                return GetLogFilesHelper(logfileRequests);
            , state);

        return task.ContinueWith(res => callback(task));
    

    public LogFile[] EndGetLogFiles(IAsyncResult result)
    
        var castResult = result as Task<LogFile[]>;
        return castResult.Result;
    

    private LogFile[] GetLogFilesHelper(LogFileRequest[] logfileRequests)
    
        //Long-running method that gets the log files
    

    //snip...

这是客户端代码:

public class InformationServiceConnection : WcfDurableConnection<IInformationService> //WcfDurableConnection is one of our internal classes

    //snip...

    public void GetServiceLogFiles(Action<LogFile[], WcfCommandResult> callback)
    
        var logfileRequests = new LogFileRequest[]
         
            new LogFileRequest(/* snip */),
            new LogFileRequest(/* snip */),
            new LogFileRequest(/* snip */),
            new LogFileRequest(/* snip */)
        ;

        ExecuteTask(x =>
            
                LogFile[] logfile = null;
                WcfCommandResult wcfResult = null;

                var asyncCallback = new AsyncCallback((result) =>
                
                    logfile = Channel.EndGetLogFiles(result);
                    callback(logfile, wcfResult);
                );

                wcfResult = RunCommand(y =>
                
                    Channel.BeginGetLogFiles(logfileRequests, asyncCallback, null);
                , x);
            );
    

    /* ExecuteTask and RunCommand are both methods that take care of
     * multithreading issues for us. I included their code below in
     * case they make a difference, but the code I'm most interested
     * in is the GetServiceLogFiles method above. */

    //snip...
    protected CancellationTokenSource ExecuteTask(Action<CancellationToken> action)
    
        CancellationTokenSource tokenSource = new CancellationTokenSource();

        ManualResetEvent lastTask;
        ManualResetEvent thisTask;
        lock (_objectLock)
        
            lastTask = _syncTask;
            thisTask = new ManualResetEvent(false);
            _syncTask = thisTask;
        

        tokenSource.Token.Register(x => ((ManualResetEvent)x).Set(), thisTask);

        var task = Task.Factory.StartNew((x) =>
        
            try
            
                lastTask.WaitOne();
                action((CancellationToken)x);
            
            catch (Exception e)
            
                LogUtility.Error(e);
            
            finally
            
                thisTask.Set();
            
        , tokenSource.Token, tokenSource.Token).HandleExceptions();

        return tokenSource;
    

    //snip...

    protected WcfCommandResult RunCommand(Action<CancellationToken> action, CancellationToken token, bool isRestarting = false)
    
        return RunCommand(x =>  action(x); return true; , token, isRestarting);
    

    protected WcfCommandResult RunCommand(Func<CancellationToken, bool> action, CancellationToken token, bool isRestarting = false)
    
        WcfCommandResult result = new WcfCommandResult();

        lock (_reconnectionLock)
        
            if (_reconnecting && !isRestarting)
            
                result.Completed = false;
                return result;
            
        


        lock (_channelLock)
        
            if (Channel == null && !_closing)
            
                token.ThrowIfCancellationRequested();
                Channel = GetNewChannel();
                var iChannel = (IClientChannel)Channel;
                var initResult = Initialize(token, false);

                if (initResult.Completed)
                
                    Connected = true;
                    LogUtility.Info(string.Format("Connected to 0 at 1", ServiceName, iChannel.RemoteAddress));
                
                else
                    LogUtility.Info(string.Format("Failed to connect to 0 at 1", ServiceName, iChannel.RemoteAddress));
            
        

        try
        
            var channel = Channel;
            token.ThrowIfCancellationRequested();
            if (channel != null)
                result.Completed = action(token);
        
        catch (FaultException e)
        
            result.Exception = e;
            result.Detail = e.GetDetail<DurableFault>();
            LogUtility.Error(result.Exception);
        
        catch (CommunicationException e)
        
            Connected = false;
            result.Exception = e;
            IClientChannel channel = ((IClientChannel)Channel);
            if (channel != null)
                channel.Abort();
            Channel = null;
            if (!_reconnecting)
                LogUtility.Error(result.Exception);
        
        catch (TimeoutException e)
        
            Connected = false;
            result.Exception = e;
            IClientChannel channel = ((IClientChannel)Channel);
            if (channel != null)
                channel.Abort();
            Channel = null;
            if (!_reconnecting)
                LogUtility.Error(result.Exception);
        
        catch (NullReferenceException e)
        
            Connected = false;
            result.Exception = e;
            IClientChannel channel = ((IClientChannel)Channel);
            if (channel != null)
                channel.Abort();
            Channel = null;
            if (!_reconnecting)
                LogUtility.WriteException("Channel is null, it has either been disposed or not setup, call BeginSetupUser to create a new channel", e);
        
        catch (ObjectDisposedException e)
        
            Connected = false;
            result.Exception = e;
            IClientChannel channel = ((IClientChannel)Channel);
            if (channel != null)
                channel.Abort();
            Channel = null;
            if (!_reconnecting)
                LogUtility.Error(result.Exception);
        
        catch (InvalidOperationException e)
        
            Connected = false;
            result.Exception = e;
            IClientChannel channel = ((IClientChannel)Channel);
            if (channel != null)
                channel.Abort();
            Channel = null;
            if (!_reconnecting)
                LogUtility.Error(result.Exception);
        

        return result;
    

    //snip...

【问题讨论】:

【参考方案1】:

即使是异步调用,您的配置文件中也设置了超时。如果需要很长时间才能响应,您可能应该增加它。我认为默认是 1 分钟。在 Visual Studio 中,转到工具-> WCF 服务配置编辑器以轻松更改值。

如果您想查看配置的外观,这也可能对您有所帮助:Increasing the timeout value in a WCF service

您可以在该配置文件或后面的代码中设置它。

【讨论】:

这是有道理的。我会考虑按照您的建议更改超时期限。感谢您的回复! 对于那些稍后阅读本文的人:本文解释了如何以编程方式更改更改超时值:codeproject.com/Articles/28265/WCF-Operation-Timeout-Exception

以上是关于异步 WCF 服务超时的主要内容,如果未能解决你的问题,请参考以下文章

WCF服务,如何增加超时?

WCF 服务超时

超时 WCF 服务

WCF 不活动超时

WCF 超时客户端与服务器

更改 WCF 默认超时