2012-02-04 88 views
4

我有一個異步操作取決於另一臺服務器,它需要大部分隨機的時間來完成。在異步操作正在運行的同時,「主線程」中還有一些處理正在進行,這也需要花費一定的時間來完成。異步延遲超時任務

主線程啓動異步任務,執行它的主要任務,並在最後檢查異步任務的結果。

異步線程拉取數據並計算對主線程完成不重要的字段。然而,如果計算能夠完成而沒有減慢的主線程,則該數據將會很好(並且應該被包括)。

我想設置異步任務,在最小運行2秒,但 採取一切開始和主要任務結束之間的可用時間。 這是一個'延遲超時',因爲如果超過第二個運行時間並且實際上正在請求結果,則它僅超時。 (異步 任務應採取更大的2秒,或 主要任務的總運行時間)

EDIT(試圖澄清的要求):如果異步任務有機會運行持續2秒鐘,它根本不應該阻塞主線程。主線程必須允許異步任務至少運行2秒。此外,如果主線程需要超過2秒才能完成,則應允許異步任務與主線程一樣運行。

我設計了一個可以工作的包裝,但是我更喜歡一個實際上是Task類型的解決方案。看到我下面的包裝解決方案。

public class LazyTimeoutTaskWrapper<tResult> 
{ 
    private int _timeout; 
    private DateTime _startTime; 
    private Task<tResult> _task; 
    private IEnumerable<Action> _timeoutActions; 

    public LazyTimeoutTaskWrapper(Task<tResult> theTask, int timeoutInMillis, System.DateTime whenStarted, IEnumerable<Action> onTimeouts) 
    { 
     this._task = theTask; 
     this._timeout = timeoutInMillis; 
     this._startTime = whenStarted; 
     this._timeoutActions = onTimeouts; 
    } 

    private void onTimeout() 
    { 
     foreach (var timeoutAction in _timeoutActions) 
     { 
      timeoutAction(); 
     } 
    } 

    public tResult Result 
    { 
     get 
     { 
      var dif = this._timeout - (int)System.DateTime.Now.Subtract(this._startTime).TotalMilliseconds; 
      if (_task.IsCompleted || 
       (dif > 0 && _task.Wait(dif))) 
      { 
       return _task.Result; 
      } 
      else 
      { 
       onTimeout(); 
       throw new TimeoutException("Timeout Waiting For Task To Complete"); 
      } 
     } 
    } 

    public LazyTimeoutTaskWrapper<tNewResult> ContinueWith<tNewResult>(Func<Task<tResult>, tNewResult> continuation, params Action[] onTimeouts) 
    { 
     var result = new LazyTimeoutTaskWrapper<tNewResult>(this._task.ContinueWith(continuation), this._timeout, this._startTime, this._timeoutActions.Concat(onTimeouts)); 
     result._startTime = this._startTime; 
     return result; 
    } 
} 

有沒有人比這個包裝有更好的解決方案?

+1

我很好奇你爲什麼想要後臺任務花費比完成實際工作所需的時間更長的時間。 – 2012-02-04 17:12:19

+0

這不應該花費比實際工作時間更長的時間。主線程只需要等待至多2秒,但如果主線程太忙而不能立即需要結果,則主線程要允許它運行超過2秒。如果主線程花費了2秒多的時間完成,主線程根本不想等待。 – hannasm 2012-02-04 17:28:54

回答

1

我總是會開始一個2秒的任務,當它完成時,將您的計算標記爲已取消。這爲您節省了奇怪的「差異」時間計算。這裏是一些代碼:

Task mainTask = ...; //represents your main "thread" 
Task computation = ...; //your main task 
Task timeout = TaskEx.Delay(2000); 

TaskCompletionSource tcs = new TCS(); 

TaskEx.WhenAll(timeout, mainTask).ContinueWith(() => tcs.TrySetCancelled()); 
computation.ContinueWith(() => tcs.TryCopyResultFrom(computation)); 

Task taskToWaitOn = tcs.Task; 

這是僞代碼。我只想展示技術。

TryCopyResultFrom是爲了將計算結果複製到TaskCompletionSource tcs中,方法是調用TrySetResult()。

你的應用程序只是使用taskToWaitOn。它會過渡到2秒後取消。如果計算較早完成,它將收到結果。

+0

我從來沒有見過'TryCopyResultFrom()',顯然谷歌也沒有。這個從哪裏來?當然不是來自.Net(無論是4.0還是4.5)。 – svick 2012-02-04 17:22:18

+1

另外,我認爲這不是問什麼。只有在主線程已經完成的情況下,計算應在2秒後取消。 – svick 2012-02-04 17:23:15

+0

借調svick它需要至少等待2秒,如果主線程需要100秒,則需要100秒。 – hannasm 2012-02-04 17:26:52

1

我不認爲你可以讓Task<T>表現這種方式,因爲Result不是virtual並且也沒有任何其他方式來改變它的行爲。

我也認爲你甚至不應該嘗試這樣做。 Result屬性的合同是等待結果(如果尚不可用)並返回。這不是取消任務。這樣做會很混亂。如果你取消了這個任務,我認爲你應該從代碼中明顯看出你正在做這件事。

如果我這樣做,我會創造了Task<T>的包裝,但它應該是這樣的:

class CancellableTask<T> 
{ 
    private readonly Func<CancellationToken, T> m_computation; 
    private readonly TimeSpan m_minumumRunningTime; 

    private CancellationTokenSource m_cts; 
    private Task<T> m_task; 
    private DateTime m_startTime; 

    public CancellableTask(Func<CancellationToken, T> computation, TimeSpan minumumRunningTime) 
    { 
     m_computation = computation; 
     m_minumumRunningTime = minumumRunningTime; 
    } 

    public void Start() 
    { 
     m_cts = new CancellationTokenSource(); 
     m_task = Task.Factory.StartNew(() => m_computation(m_cts.Token), m_cts.Token); 
     m_startTime = DateTime.UtcNow; 
    } 

    public T Result 
    { 
     get { return m_task.Result; } 
    } 

    public void CancelOrWait() 
    { 
     if (m_task.IsCompleted) 
      return; 

     TimeSpan remainingTime = m_minumumRunningTime - (DateTime.UtcNow - m_startTime); 

     if (remainingTime <= TimeSpan.Zero) 
      m_cts.Cancel(); 
     else 
     { 
      Console.WriteLine("Waiting for {0} ms.", remainingTime.TotalMilliseconds); 
      bool finished = m_task.Wait(remainingTime); 
      if (!finished) 
       m_cts.Cancel(); 
     } 
    } 
} 

注意,計算有CancellationToken參數。這是因爲你不能強制取消(沒有骯髒的技巧,如Thread.Abort()),並且計算必須明確支持它,理想的情況是在適當的時候執行cancellationToken.ThrowIfCancellationRequested()

+0

查看我的頂級編輯,您希望如何實現這些要求? – hannasm 2012-02-04 17:41:21

+0

查看我的編輯,瞭解我可能如何做到這一點。 – svick 2012-02-04 18:08:30