如何使用谓词实现Task.WhenAny()

我想异步执行几个任务,每个任务都会运行http请求,可以抛出exception或安全结束。 我需要在第一个任务成功完成或所有任务都失败时完成。 请指教。

public static Task GetFirstResult( ICollection>> taskFactories, Predicate predicate) where T : class { var tcs = new TaskCompletionSource(); var cts = new CancellationTokenSource(); int completedCount = 0; // in case you have a lot of tasks you might need to throttle them //(eg 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; } 

样品用法:

 using System.Net.Http; var client = new HttpClient(); var response = await GetFirstResult( new Func>[] { 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}"); 

等待任何任务并在满足条件时返回任务。 否则,请等待其他任务,直到没有其他任务等待。

 public static async Task WhenAny( IEnumerable tasks, Predicate 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; } 

简单检查一下

 var tasks = new List { 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" ); 
 public static Task> WhenFirst(IEnumerable> tasks, Func, bool> predicate) { if (tasks == null) throw new ArgumentNullException(nameof(tasks)); if (predicate == null) throw new ArgumentNullException(nameof(predicate)); var tasksArray = (tasks as IReadOnlyList>) ?? 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>(); var count = tasksArray.Count; Action> 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; 

请注意,这不会传播失败任务的exception(就像WhenAny那样)。

您还可以为非通用Task创建此版本。