2016-07-10 14 views
2

私はいくつかのタスクを非同期に実行したい、各タスクは、例外をスローするか、安全に終了することができるHTTP要求を実行します。私は、最初のタスクが正常に完了したとき、またはすべてのタスクが失敗したときに完了する必要があります。ご意見をお聞かせください。どのように述語でTask.WhenAny()を実装する

+0

を自分で試してみて、あなたが遭遇した特定の問題のために頼みます。少なくとも努力を示す。 – CSharpie

答えて

0
public static Task<T> GetFirstResult<T>(
    ICollection<Func<CancellationToken, Task<T>>> taskFactories, 
    Predicate<T> predicate) where T : class 
{ 
    var tcs = new TaskCompletionSource<T>(); 
    var cts = new CancellationTokenSource(); 

    int completedCount = 0; 
    // in case you have a lot of tasks you might need to throttle them 
    //(e.g. so you don't try to send 99999999 requests at the same time) 
    // see: http://stackoverflow.com/a/25877042/67824 
    foreach (var taskFactory in taskFactories) 
    { 
     taskFactory(cts.Token).ContinueWith(t => 
     { 
      if (t.Exception != null) 
      { 
       Console.WriteLine($"Task completed with exception: {t.Exception}"); 
      } 
      else if (predicate(t.Result)) 
      { 
       cts.Cancel(); 
       tcs.TrySetResult(t.Result); 
      } 

      if (Interlocked.Increment(ref completedCount) == taskFactories.Count) 
      { 
       tcs.SetException(new InvalidOperationException("All tasks failed")); 
      } 

     }, cts.Token); 
    } 

    return tcs.Task; 
} 

サンプルを使用するため

public static async Task<Task> WhenAny(IEnumerable<Task> tasks, Predicate<Task> condition) 
{ 
    var tasklist = tasks.ToList(); 
    while (tasklist.Count > 0) 
    { 
     var task = await Task.WhenAny(tasklist); 
     if (condition(task)) 
      return task; 
     tasklist.Remove(task); 
    } 
    return null; 
} 

簡単なチェック:

using System.Net.Http; 
var client = new HttpClient(); 
var response = await GetFirstResult(
    new Func<CancellationToken, Task<HttpResponseMessage>>[] 
    { 
     ct => client.GetAsync("http://microsoft123456.com", ct), 
     ct => client.GetAsync("http://microsoft123456.com", ct), 
     ct => client.GetAsync("http://microsoft123456.com", ct), 
     ct => client.GetAsync("http://microsoft123456.com", ct), 
     ct => client.GetAsync("http://microsoft123456.com", ct), 
     ct => client.GetAsync("http://microsoft123456.com", ct), 
     ct => client.GetAsync("http://microsoft123456.com", ct), 
     ct => client.GetAsync("http://microsoft.com", ct), 
     ct => client.GetAsync("http://microsoft123456.com", ct), 
     ct => client.GetAsync("http://microsoft123456.com", ct), 
    }, 
    rm => rm.IsSuccessStatusCode); 
Console.WriteLine($"Successful response: {response}"); 
+2

クイックレスポンスと偉大なエンジニアリングソリューションの皆様に本当にありがとうございます。あなたは私をたくさん助けました。 –

+0

私はちょうど保護者です - あなたは本当に私のメンターに感謝してください(そしてアップアップ!)http://stackoverflow.com/a/38289396/67824;) –

2
public static Task<Task<T>> WhenFirst<T>(IEnumerable<Task<T>> tasks, Func<Task<T>, bool> predicate) 
{ 
    if (tasks == null) throw new ArgumentNullException(nameof(tasks)); 
    if (predicate == null) throw new ArgumentNullException(nameof(predicate)); 

    var tasksArray = (tasks as IReadOnlyList<Task<T>>) ?? tasks.ToArray(); 
    if (tasksArray.Count == 0) throw new ArgumentException("Empty task list", nameof(tasks)); 
    if (tasksArray.Any(t => t == null)) throw new ArgumentException("Tasks contains a null reference", nameof(tasks)); 

    var tcs = new TaskCompletionSource<Task<T>>(); 
    var count = tasksArray.Count; 

    Action<Task<T>> continuation = t => 
     { 
      if (predicate(t)) 
      { 
       tcs.TrySetResult(t); 
      } 
      if (Interlocked.Decrement(ref count) == 0) 
      { 
       tcs.TrySetResult(null); 
      } 
     }; 

    foreach (var task in tasksArray) 
    { 
     task.ContinueWith(continuation); 
    } 

    return tcs.Task; 
} 

使用例:これは失敗したタスクの例外を伝播していないこと

var task = await WhenFirst(tasks, t => t.Status == TaskStatus.RanToCompletion); 

if (task != null) 
    var value = await task; 

注意(WhenAnyがない場合と同じように)。

非汎用のTaskのバージョンを作成することもできます。

2

任意のタスクを待って、条件が満たされている場合はタスクを返します。そうでなければ、待つべきタスクがなくなるまで、他のタスクを再び待つ。その

var tasks = new List<Task> { 
    Task.FromException(new Exception()), 
    Task.FromException(new Exception()), 
    Task.FromException(new Exception()), 
    Task.CompletedTask, }; 

var completedTask = WhenAny(tasks, t => t.Status == TaskStatus.RanToCompletion).Result; 

if (tasks.IndexOf(completedTask) != 3) 
    throw new Exception("not expected"); 
関連する問題