2012-07-17 142 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