首页 > 解决方案 > 调用方法包含UI交互时如何等待所有任务?

问题描述

首先,我知道有WaitAll和非阻塞WhenAll. 我目前的问题略有不同,但希望我可以使用其中一种方法。

所以,我有一个从外部 WebApi 加载一个大而复杂的对象的表单。加载以块的形式完成,如下所示:

public void LoadObject()
{
    LoadChunk1();
    LoadChunk2();
    LoadChunk3();
    LoadChunk4();
    LoadChunk5();
}  

public async void LoadChunk1()
{
    //Clear controls, data bindings etc...

    await Task.Run(() =>
    {
        chunk = someService.LoadChunk1();
    });

    //Update controls, data bindings etc...
}

这同样适用于所有方法。这一切都很好。现在我需要在主加载方法中添加一个进度条,以便用户知道何时加载了所有内容。此外,这些方法可能经常从表单中的其他位置调用,而不一定是从 main 方法调用。

同步加载不是一个选项,因为 UI 会阻塞几秒钟,所以一切都必须保持原样。

有没有办法WhenAll在任务数组上使用?或者在这种情况下是不可能的,因为我访问了 UI?如果我使用BeginInvoke怎么办?

标签: c#winformstask

解决方案


我解决这个问题的方法是让任何报告进度的方法都采用 Progress 对象。这些对象可以包含 a:计数器(双精度)或 b:进度对象列表。这允许该方法要么只是增加计数器,要么将对象分成几个并将每个子进度分配给不同的方法。进度对象将具有计算整个树的总进度的方法。

所以你的例子看起来像这样:

public void LoadObject(Progress progress)
{
    var subProgress = progress.Split(5)
    LoadChunk1(subProgress[0] );
    LoadChunk2(subProgress[1]);
    LoadChunk3(subProgress[2]);
    LoadChunk4(subProgress[3]);
    LoadChunk5(subProgress[4]);
}  

public async void LoadChunk1(Progress progress)
{
    await Task.Run(() =>
    {
        chunk = someService.LoadChunk1();
        progress.Value = 1; // This chunk is complete
    });
    
}

将其与在主线程上运行的单独轮询器相结合,每 0.2 秒左右检查一次整体进度,并更新进度条。这允许以任何顺序进行处理,并且您可以从任何线程报告进度。

如果您有一个模态进度对话框,您可以使用它WhenAll来生成一个在加载所有块时完成的任务,并在任务完成时关闭对话框。


推荐阅读