等待在GUI中显示多个任务

本文关键字:任务 显示 GUI 等待 | 更新日期: 2023-09-27 18:22:28

假设我有两个资源AB,我想通过MVVM(此==视图模型)向用户显示这两个资源

this.A = GetA();
this.B = GetB();

一旦我开始使用TPL:

this.A = await GetAAsync();
this.B = await GetBAsync();

这开始得到A。当A准备好时,它显示A,并继续对B执行同样的操作——这不是一个很好的解决方案。最好是:

var taskA = GetAAsync();
var taskB = GetBAsync();
this.A = await taskA;
this.B = await taskB;

现在,这开始获得A,开始获取B并等待A。当A准备好时,它显示A并等待B,直到它也显示出来看起来不错,但是,如果A有时比B花费更多的时间加载呢?

我如何实现以下场景:

  • 开始加载A
  • 开始加载B
  • 当其中一个准备好了,就展示出来
  • 当另一个准备好了,也展示出来

等待在GUI中显示多个任务

最简单的选择就是将异步操作与更新本身结合起来,然后你可以启动并等待这两个操作,但当每个操作完成时,它都会更新它需要的内容。你可以通过在每个方法的末尾"粘贴"更新来做到这一点,或者创建一个异步包装方法,如下所示:

async Task UpdateAAsync()
{
    A = await GetAAsync();
}
async Task UpdateBAsync()
{
    B = await GetBAsync();
}

await Task.WhenAll(UpdateAAsync(), UpdateBAsync());

或者,正如svick所建议的,您可以使用包装器lambda表达式来代替包装器方法:

Func<Task> updateAAsync = async () => A = await GetAAsync();
Func<Task> updateBAsync = async () => B = await GetBAsync();
await Task.WhenAll(updateAAsync(), updateBAsync());

您可以分别安排这两项任务的延续。

var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
GetAAsync().ContinueWith(t => this.A = t.Result, uiScheduler);
GetBAsync().ContinueWith(t => this.B = t.Result, uiScheduler);

这样,如果这是一个WinForms或WPF应用程序,则延续将为您调度到UI线程。


还要注意,我没有为调用线程编写任何代码来等待这两个任务完成。如果您要阻塞调用线程(不推荐),而该线程恰好是UI线程,那么您将遇到死锁情况:

var taskA = GetAAsync().ContinueWith(t => this.A = t.Result, uiScheduler);
var taskB = GetBAsync().ContinueWith(t => this.B = t.Result, uiScheduler);
Task.WaitAll(taskA, taskB); 

最后一行将阻塞UI线程。当任务试图在UI线程(被阻止)上调度延续时,这将阻止任务完成。

您可以将Task类的静态方法与wait结合使用,以防止阻塞接口:

var taskA = GetAAsync();
var taskB = GetBAsync();
await Task.WhenAny(new [] { taskA,taskB });
//first task completed
await Task.WhenAll(new [] { taskA,taskB });
//all tasks completed

这个解决方案会变得更复杂——如果任务多于两个,那么您应该寻找一个Task.WhenAll适合您需求的解决方案,或者使用类似Backgroundworker的东西。

MSDN上有一篇文章讨论了这个问题。请在此处阅读。

基本上,您使用WhenAny的结果来查看哪个Task已完成,并将其从包含任务的列表中删除。然后重复,直到列表为空:

IEnumerable<Task<int>> downloadTasksQuery = ListOfTasks();
while (downloadTasks.Count > 0)
{
    Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
    downloadTasks.Remove(firstFinishedTask);
    // process the result from firstFinishedTask
}

如果有两个以上的任务,这也会进行缩放。