2012-07-17 144 views
1

我们有一个异步WCF服务操作,它从我们系统的所有不同组件获取日志文件并将它们发送到客户端。由于这可能需要一段时间,如果其中一个组件不能正常工作,如果这个功能不会超时,它会很好,但它不应该导致客户端挂起。异步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... 
} 

回答

2

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

,如果你想看到的配置应该是什么这样也可以帮助你:Increasing the timeout value in a WCF service

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

+0

这是有道理的。我会考虑改变你的建议超时时间。感谢您的答复! – Kevin 2012-07-17 19:41:31

+1

对于那些稍后阅读:本文解释如何以编程方式更改更改超时值:http://www.codeproject.com/Articles/28265/WCF-Operation-Timeout-Exception – Kevin 2012-07-17 20:38:46