我需要在控制台应用程序中运行多个异步任务,并在进一步处理之前等待它们全部完成。

有很多文章,但我似乎越读越困惑。我已经阅读并理解了Task库的基本原理,但我显然在某个地方遗漏了一个链接。

我知道可以将任务串联起来,这样它们就可以在另一个任务完成后开始(这几乎是我读过的所有文章的场景),但我希望所有任务同时运行,并且我想知道它们都完成后的情况。

对于这样的场景,最简单的实现是什么?


当前回答

你可以创建很多任务,比如:

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());

其他回答

还有另一个答案……但我通常会遇到这样的情况,当我需要同时加载数据并将其放入变量中,比如:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

你可以使用WhenAll,它将返回一个可等待的任务,或者WaitAll,它没有返回类型,将阻止进一步的代码执行,类似于线程。休眠,直到所有任务都完成、取消或出错。

WhenAll WaitAll
Any of the supplied tasks completes in a faulted state A task with the faulted state will be returned. The exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks. An AggregateException will be thrown.
None of the supplied tasks faulted but at least one of them was canceled The returned task will end in the TaskStatus.Canceled state An AggregateException will be thrown which contains an OperationCanceledException in its InnerExceptions collection
An empty list was given An ArgumentException will be thrown The returned task will immediately transition to a TaskStatus.RanToCompletion State before it's returned to the caller.
Doesn't block the current thread Blocks the current thread

例子

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

如果你想以特定的顺序运行任务,你可以从这个答案中得到灵感。

应该有一个比公认的答案更简洁的解决方案。它不应该只用三步就能同时运行多个任务并获得结果。

创建任务 等待Task.WhenAll(任务) 获取任务结果(例如task1.Result)

这里有一个方法,可以把这个问题简化为两个步骤:

    public async Task<Tuple<T1, T2>> WhenAllGeneric<T1, T2>(Task<T1> task1, Task<T2> task2)
    {
        await Task.WhenAll(task1, task2);
        return Tuple.Create(task1.Result, task2.Result);
    }

你可以这样使用它:

var taskResults = await Task.WhenAll(DoWorkAsync(), DoMoreWorkAsync());
var DoWorkResult = taskResults.Result.Item1;
var DoMoreWorkResult = taskResults.Result.Item2;

这样就不需要临时任务变量了。使用它的问题是,虽然它适用于两个任务,但您需要为三个任务或任何其他数量的任务更新它。而且,如果其中一个任务没有返回任何东西,它也不能很好地工作。实际上,.Net库应该提供一些可以做到这一点的东西

如果你正在使用async/await模式,你可以像这样并行运行几个任务:

public async Task DoSeveralThings()
{
    // Start all the tasks
    Task first = DoFirstThingAsync();
    Task second = DoSecondThingAsync();

    // Then wait for them to complete
    var firstResult = await first;
    var secondResult = await second;
}

你可以创建很多任务,比如:

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());