您的代码与this article 中建议的代码非常相似。主要区别在于它们使用通用类型的任务,因此可以等待它们查看返回的数据。
将那篇文章中的模式应用到您的代码中会如下所示:
async Task Main()
{
var taskList = new[] { "Task 1", "Task 2" }
.Select(SomethingAsync)
.ToList();
await AwaitAllTasks(taskList);
Console.WriteLine("All tasks completed");
}
static async Task AwaitAllTasks(List<Task<string>> tasksList)
{
/// Use a wait any loop to find any tasks that have completed
/// Warning: this is O(N2) and will not scale for thousands of tasks
while (tasksList.Count > 0)
{
var t = await Task.WhenAny(tasksList);
tasksList.Remove(t);
var theValue = await t;
Console.WriteLine($"{theValue} completed.", theValue);
}
}
static async Task<string> SomethingAsync(string theValue)
{
await Task.Yield();
Console.WriteLine(theValue);
// Do a CPU bound activity
for (int i = 0; i < 1000000; i++)
;
return theValue;
}
几点说明:
- 不需要AwaitAllTasks 做Task.Run()。它不是 CPU 密集型的。
- C# 现在允许你有一个异步 Main 方法,所以你可以await 而不是阻塞在主线程上。
关于这个:
它们可能不一定完成,因为可能会引发异常,因此我不能依赖返回码。
如果抛出异常,任务将完成,但尝试await 会导致抛出异常。因此,您可能想在 await t 周围添加一个 try/catch。
如果任务可能永远不会完成,您可能需要使用取消令牌来避免无限期地等待它们。
更新
如果无论任务是否成功,您都需要跟踪与任务相关的特定信息,则需要使用适当的集合(如字典)来保持这种相关性。例如:
async Task Main()
{
var tasks = new[] { "Task 1", "Task 2", "Task 3" }
.ToDictionary(name => SomethingAsync(name));
await AwaitAllTasks(tasks);
Console.WriteLine("All tasks completed");
}
static async Task AwaitAllTasks(IDictionary<Task, string> tasks)
{
/// Use a wait any loop to find any tasks that have completed
/// Warning: this is O(N2) and will not scale for thousands of tasks
while (tasks.Count > 0)
{
var t = await Task.WhenAny(tasks.Keys);
var theValue = tasks[t];
tasks.Remove(t);
try
{
await t;
Console.WriteLine($"{theValue} completed.");
}
catch(Exception)
{
Console.WriteLine($"{theValue} had a failure.");
}
}
}
static async Task SomethingAsync(string theValue)
{
await Task.Yield();
Console.WriteLine(theValue);
if(theValue == "Task 2"){
throw new InvalidOperationException("Foo");
}
// Do a CPU bound activity
for (int i = 0; i < 1000000; i++)
;
}
更新 2
另一种选择是避免使用单独的方法来单独等待所有任务,而只需将您希望在每个任务之后执行的操作视为每个任务的延续。我通常发现最好将一组异步任务视为一种数据流,其中每个任务的结果都可以传递给另一个操作,LINQ 风格,直到你准备好将它们的结果重新组合在一起.
async Task Main()
{
var continuations =
from theValue in new[] { "Task 1", "Task 2", "Task 3" }
// AsParallel() ensures the tasks are _begun_ in a multithreaded way.
// Use it if SomethingAsync() might block the CPU for a while before
// yielding the thread.
.AsParallel()
let task = SomethingAsync(theValue)
select ContinueSomething(task, theValue);
try
{
await Task.WhenAll(continuations);
Console.WriteLine("All tasks completed");
}
catch (Exception)
{
Console.WriteLine("Some tasks failed");
}
}
static async Task ContinueSomething(Task somethingTask, string theValue)
{
try
{
await somethingTask;
Console.WriteLine($"{theValue} completed.");
}
catch (Exception)
{
Console.WriteLine($"{theValue} had a failure.");
}
}
当然,async/await 主要是语法糖,用于您可以做的事情。 ContinueSomething() 可以改写为:
static Task ContinueSomething(Task somethingTask, string theValue)
{
return somethingTask.ContinueWith(t =>
{
if (t.IsFaulted)
{
Console.WriteLine($"{theValue} had a failure.");
}
else
{
Console.WriteLine($"{theValue} completed.");
}
});
}
或者你甚至可以将它内联到你原来的 LINQ 语句中:
async Task Main()
{
var continuations =
from theValue in new[] { "Task 1", "Task 2", "Task 3" }.AsParallel()
let task = SomethingAsync(theValue)
select task.ContinueWith(t =>
{
if (t.IsFaulted)
{
Console.WriteLine($"{theValue} had a failure.");
}
else
{
Console.WriteLine($"{theValue} completed.");
}
});
await Task.WhenAll(continuations);
}
当然,这一切都假设您的操作本质上是异步的。如果您的实际用例与您发布的任何情况相似,几乎完全受 CPU 限制,您可能想跳过 async 的内容,让 Parallel.ForEach() 为您处理并行性:
void Main()
{
Parallel.ForEach(new[] { "Task 1", "Task 2", "Task 3" }, theValue =>
{
try
{
SomethingAsync(theValue);
Console.WriteLine($"{theValue} completed.");
}
catch (Exception)
{
Console.WriteLine($"{theValue} had a failure.");
}
});
Console.WriteLine("All tasks completed");
}
static void SomethingAsync(string theValue)
{
Console.WriteLine(theValue);
if (theValue == "Task 2")
{
throw new InvalidOperationException("Foo");
}
// Do a CPU bound activity
for (int i = 0; i < 1000000; i++)
;
}
重点是:有很多不同的方法可以做你想做的事情,哪一种最好取决于具体情况。