等待在GUI中显示多个任务
本文关键字:任务 显示 GUI 等待 | 更新日期: 2023-09-27 18:22:28
假设我有两个资源A
和B
,我想通过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
- 当其中一个准备好了,就展示出来
- 当另一个准备好了,也展示出来
最简单的选择就是将异步操作与更新本身结合起来,然后你可以启动并等待这两个操作,但当每个操作完成时,它都会更新它需要的内容。你可以通过在每个方法的末尾"粘贴"更新来做到这一点,或者创建一个异步包装方法,如下所示:
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的东西。
基本上,您使用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
}
如果有两个以上的任务,这也会进行缩放。