2016-05-30 2 views
6

は、私は、タスクのカップルを持っていると言うことができます。だから、基本的な解決策のようなもの書くことです:正常に終了した最初のタスクを取得するデフォルトの方法はありますか?</p> <pre><code>void Sample(IEnumerable<int> someInts) { var taskList = someInts.Select(x => DownloadSomeString(x)); } async Task<string> DownloadSomeString(int x) {...} </code></pre> <p>は、私が最初に成功したタスクの結果を得るためにしたい:

var taskList = someInts.Select(x => DownloadSomeString(x)); 
string content = string.Empty; 
Task<string> firstOne = null; 
while (string.IsNullOrWhiteSpace(content)){ 
    try 
    { 
     firstOne = await Task.WhenAny(taskList); 
     if (firstOne.Status != TaskStatus.RanToCompletion) 
     { 
      taskList = taskList.Where(x => x != firstOne); 
      continue; 
     } 
     content = await firstOne; 
    } 
    catch(...){taskList = taskList.Where(x => x != firstOne);} 
} 

をしかし、この解決策はN +(N -1)+ ... + Kタスクを実行しているようです。 NsomeInts.CountKの場合は、タスクの中で最初に成功したタスクの位置です。したがって、WhenAnyによって取得されたタスク以外のすべてのタスクが再実行されます。 したがって、実行中の最大数がNのタスクで正常に終了した最初のタスクを取得する方法はありますか?最初の1が正常に終了したときにあなたがする必要があるのは、TaskCompletionSourceを作成してタスクのそれぞれに継続を追加し、それを設定されている

+0

タスクを並行して実行するのではなく、並行して実行したいと思うようです。 –

+0

あなたがそれらを並行して実行したいなら、 'var taskList = someInts.Select(x => DownloadSomeString(x)).ToList()'がうまくいくはずです。 –

+0

それらを順番に実行したい場合は、単純な 'for'ループがジョブを実行する必要があります。 –

答えて

4

「最初に成功したタスク」の問題は、すべてのタスクが失敗した場合にどうするかのですか?really bad idea to have a task that never completesです。

すべてが失敗した場合、最後のタスクの例外を伝播したいと仮定します。このことを念頭に置いて、私はこのような何かが適切であろうと言うでしょう:

async Task<Task<T>> FirstSuccessfulTask(IEnumerable<Task<T>> tasks) 
{ 
    Task<T>[] ordered = tasks.OrderByCompletion(); 
    for (int i = 0; i != ordered.Length; ++i) 
    { 
    var task = ordered[i]; 
    try 
    { 
     await task.ConfigureAwait(false); 
     return task; 
    } 
    catch 
    { 
     if (i == ordered.Length - 1) 
     return task; 
     continue; 
    } 
    } 
    return null; // Never reached 
} 

このソリューションはmy AsyncEx librarypartあるOrderByCompletion extension methodに基づいています。代わりの実装は、Jon SkeetStephen Toubによっても存在します。

+0

'OrderByCompletion'はすべてのタスクが最初に完了するのを待っていませんか? –

+1

私は、私の答えの歴史を見れば、以前のリビジョンでこの一般的なアプローチを使用しました。あなたが値を計算するために示したよりも実際にはずっと簡単ですが、正直なところ、この場合は継続を手作業で追加するほうがずっと簡単です。 – Servy

+0

@ YacoubMassadいいえ、それはメソッドの要点です。それは完全に非同期であり、タスクが終了する順序でタスクを返すだけであると想像することができます(技術的には何も起こっていないにもかかわらず)。 – Servy

4

(成功したタスクが可能になる場合は、最後の1):

public static Task<T> FirstSuccessfulTask<T>(IEnumerable<Task<T>> tasks) 
{ 
    var taskList = tasks.ToList(); 
    var tcs = new TaskCompletionSource<T>(); 
    int remainingTasks = taskList.Count; 
    foreach(var task in taskList) 
    { 
     task.ContinueWith(t => 
      if(task.Status == TaskStatus.RanToCompletion) 
       tcs.TrySetResult(t.Result)); 
      else 
       if(Interlocked.Decrement(ref remainingTasks) == 0) 
        tcs.SetException(new AggregateException(
         tasks.SelectMany(t => t.Exception.InnerExceptions)); 
    } 
    return tcs.Task; 
} 

そして、結果のない作業用のバージョン:

public static Task FirstSuccessfulTask(IEnumerable<Task> tasks) 
{ 
    var taskList = tasks.ToList(); 
    var tcs = new TaskCompletionSource<bool>(); 
    int remainingTasks = taskList.Count; 
    foreach(var task in taskList) 
    { 
     task.ContinueWith(t => 
      if(task.Status == TaskStatus.RanToCompletion) 
       tcs.TrySetResult(true)); 
      else 
       if(Interlocked.Decrement(ref remainingTasks) == 0) 
        tcs.SetException(new AggregateException(
         tasks.SelectMany(t => t.Exception.InnerExceptions)); 
    } 
    return tcs.Task; 
} 
+0

(task.Status == TaskStatus.RanToCompletion)を意味しましたか? – bodangly

+0

@ bodanglyしました。 – Servy

+0

'Task >'を返すようにすれば、エラー信号を単純化できます。結果として失敗したタスクを返すか、またはnullを返すことができます。それは、発信者が何を達成したいかによって異なります。 – usr

2

どんなタスクも待つことは、それがRanToCompletion状態にあるかどうかをチェックし、そうでない場合は、すでに完了しているタスク以外のタスクを再び待つことです。

async Task<TResult> WaitForFirstCompleted<TResult>(IEnumerable<Task<TResult>> tasks) 
    { 
     var taskList = new List<Task<TResult>>(tasks); 
     Task<TResult> firstCompleted; 
     while (taskList.Count > 0) 
     { 
      firstCompleted = await Task.WhenAny(taskList); 
      if (firstCompleted.Status == TaskStatus.RanToCompletion) 
      { 
       return firstCompleted.Result; 
      } 
      taskList.Remove(firstCompleted); 
     } 
     throw new InvalidOperationException("No task completed successful"); 
    } 
+0

これはOPがすでにやっていることです。 – Servy

+0

@Servyここで提供されるすべてのソリューションは、(異なるアプローチで)同じように実行されました。 - N個の実行中のタスクを待ち、最初に成功したタスク/結果を返します。これ(および他のもの)は、質問に記載されているようにタスクを再実行しませんでした。 –

0

コンパイルエラーといくつかの落とし穴が含まれているため@Servyのコードが変更されています。私の変異体である:それはすでに我々がで作業することができ、コレクション、それはコンパイル(大きな利点)だといくつかの理由で例外が持っていないとき、それは状況を扱うので、我々は我々の入力をToListする必要はありません

public static class AsyncExtensions 
{ 
    public static Task<T> GetFirstSuccessfulTask<T>(this IReadOnlyCollection<Task<T>> tasks) 
    { 
     var tcs = new TaskCompletionSource<T>(); 
     int remainingTasks = tasks.Count; 
     foreach (var task in tasks) 
     { 
      task.ContinueWith(t => 
      { 
       if (task.Status == TaskStatus.RanToCompletion) 
        tcs.TrySetResult(t.Result); 
       else if (Interlocked.Decrement(ref remainingTasks) == 0) 
        tcs.SetException(new AggregateException(
         tasks.SelectMany(t2 => t2.Exception?.InnerExceptions ?? Enumerable.Empty<Exception>()))); 
      }); 
     } 
     return tcs.Task; 
    } 
} 

innter例外(完全に可能です)。

+0

このアプローチ(@Servyが最初に提案した)の利点は、@Sir Rufoのものと比べて何ですか?なぜあなたはこれを使っていますか? – 3615

+0

まず、コンパイルします。第二に、すでにコレクションに入っているタスクをToListする必要はありません。これは大きなオーバーヘッドではありませんが、元のコードにはそれがあります。そして、私はコードとの契約がより透明でなければならないと考えています。ソースコレクションで 'ToList'を実行している場合、実際にコレクションが必要なので、クエリや何かを処理することができます。 –

関連する問題

 関連する問題