2012-02-04 19 views
4

私は非同期操作が別のサーバーに依存しており、完了までにほとんどの時間がかかりません。非同期操作が実行されている間に、「メインスレッド」で処理が進行しており、完了までにはランダムな時間がかかります。非同期レイジータイムアウトタスク

メインスレッドは非同期タスクを開始し、プライマリタスクを実行し、最後に非同期タスクの結果をチェックします。

非同期スレッドはデータをプルし、メインスレッドが完了するのに重要ではないフィールドを計算します。しかし、このデータは、がメインスレッドのを遅くすることなく、計算が完了できる場合には持っているといいです(含まれているはずです)。

私はセットアップに2秒間最低で実行する非同期タスクをたいと思いますが、 は開始とメインタスクの端部との間で利用可能なすべての時間を取ること。 2つ目のランタイムを超えた場合にタイムアウトするだけで、結果が実際に要求されているという点で「遅延タイムアウト」です。非同期タスクが実行する機会があった場合:

EDIT(要件を明確化しようとしているが)(非同期 タスクが2秒の大きい、または メインタスクの合計実行時間を取る必要があります) 2秒間、メインスレッドをまったくブロックすべきではありません。メインスレッドでは、非同期タスクを実行するには少なくとも2秒かかることが必要です。さらに、メインスレッドが完了するのに2秒以上かかる場合、非同期タスクはメインスレッドと同じ長さで実行する必要があります。

私は動作するラッパーを考案しましたが、私は実際にタスクタイプのソリューションを好むでしょう。以下のラッパーソリューションを参照してください。

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

バックグラウンドタスクの実際の作業に要する時間よりも長い時間がかかることがあるのはなぜですか? –

+0

実際の作業よりも時間がかかることはありません。メインスレッドはたった2秒待つだけですが、メインスレッドはすぐに結果を必要とするほどビジー状態であれば、2秒以上実行することを許可します。メインスレッドが完了するまでに2秒以上かかる場合、メインスレッドはまったく待機したくありません。 – hannasm

答えて

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は、TrySetResult()を呼び出してCompute.ResultをTaskCompletionSource tcsにコピーするためのものです。

あなたのアプリは単にtaskToWaitOnを使用します。 2秒後にキャンセルに移行します。計算が早く完了すると、その結果が返されます。

+0

私は 'TryCopyResultFrom()'を見たことがありませんし、明らかにGoogleもそうではありませんでした。これ、どこから来たの?確かに.Net(4.0か4.5のどちらか)からではありません。 – svick

+1

また、これは質問されているものではないと私は思う。メインスレッドが既に終了している場合にのみ、2秒後に計算をキャンセルする必要があります。 – svick

+0

seconding svickメインスレッドが100秒かかる場合、最小2秒、100秒待機する必要があります。 – hannasm

1

Resultvirtualではありませんし、その動作を変更する方法もありません。Task<T>をこのように動作させることはできません。

私はあなたもこれをやろうとすべきではないと思います。 Resultプロパティのコントラクトは、結果を待って(まだ利用できない場合)、それを返すことです。タスクをキャンセルするのではありません。それは非常に混乱するでしょう。タスクをキャンセルする場合は、コードから明らかにする必要があります。

私はこれを行うとしたら、私はTask<T>のラッパーを作成しますが、それは次のようになります。計算がCancellationTokenパラメータを持っていることを

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(); 
     } 
    } 
} 

は注意してください。これは、キャンセルを強制することはできません(Thread.Abort()のような汚い技なし)、計算では明示的にサポートする必要があります。理想的にはcancellationToken.ThrowIfCancellationRequested()を適切な時刻に実行する必要があります。

+0

トップ編集をご覧ください。これらの要件をどのように実装したいですか? – hannasm

+0

自分の編集方法を見てください。 – svick