并行处理任务

利奥尔·斯威萨

考虑一个返回带有某些值的任务的 API。

我想基于这些值并行更新 UI(当其中一个值准备好时,我想更新它而不等待第二个值,假设每个值的更新都是它自己的更新方法)。

public async Task MyFunc()
{
    Task<First> firstTask = MyAPI.GetFirstValue();
    Task<Second> secondTask = MyAPI.GetSecondValue();

    UpdateFirstValueUI(await firstTask)
    UpdateSecondValueUI(await secondTask)
}

该代码示例将等待第一个值,更新 UI,等待第二个值并再次更新 UI。

该场景的最佳实践是什么?我想知道是否ContinueWith是最佳实践,因为我主要在遗留代码中看到它(在有async-之前await)。

编辑一个更好的例子:假设我们有该 API 的两个实现,代码看起来像这样

public async Task MyFunc()
{
    Task<First> firstTask = null
    Task<Second> secondTask = null
    if (someCondition) 
    {
        firstTask = MyAPI1.GetFirstValue();
        secondTask = MyAPI1.GetSecondValue();
    }
    else 
    {
        firstTask = MyAPI2.GetFirstValue();
        secondTask = MyAPI2.GetSecondValue();
    }

    UpdateFirstValueUI(await firstTask)
    UpdateSecondValueUI(await secondTask)
}

现在如您所见,我不想在两个不同的分支中调用更新方法(假设我们在分支后为每个 API 拆分该方法),因此正在寻找一种仅更改更新调用的方法,以便它们可以并行发生

西奥多·祖利亚斯

ContinueWith是一种在库代码中很少使用原始方法,通常应避免在应用程序代码中使用。主要使用的问题,ContinueWith你的情况是,它要执行的延续上ThreadPool,这是不是你想要的,因为你的目的是更新UI。从 UI 线程以外的任何其他线程更新 UI是不可以的可以通过使用合适的 配置来解决这个¹ 问题,但是使用 async/await 组合来解决它要简单得多。我的建议是在项目的某个静态类中添加以下方法:ContinueWithTaskSchedulerRun

public static class UF // Useful Functions
{
    public static async Task Run(Func<Task> action) => await action();
}

此方法仅调用并等待提供的异步委托。您可以使用此方法将异步 API 调用与其 UI 更新延续相结合,如下所示:

public async Task MyFunc()
{
    Task<First> task1;
    Task<Second> task2;
    if (someCondition)
    {
        task1 = MyAPI1.GetFirstValueAsync();
        task2 = MyAPI1.GetSecondValueAsync();
    }
    else
    {
        task1 = MyAPI2.GetFirstValueAsync();
        task2 = MyAPI2.GetSecondValueAsync();
    }
    Task compositeTask1 = UF.Run(async () => UpdateFirstValueUI(await task1));
    Task compositeTask2 = UF.Run(async () => UpdateSecondValueUI(await task2));

    await Task.WhenAll(compositeTask1, compositeTask2);
}

这将确保在每个异步操作完成后立即更新 UI。

作为旁注,如果您怀疑MyAPI异步方法可能包含阻塞代码,您可以ThreadPool使用该Task.Run方法将它们卸载到,如下所示:

task1 = Task.Run(() => MyAPI1.GetFirstValueAsync());

有关为什么这是一个好主意的详尽解释,您可以查看此答案

内置Task.Run方法和UF.Run上面介绍的自定义方法之间的区别在于,在 上Task.Run调用异步委托ThreadPool,而UF.Run在当前线程上调用它。如果您对比 更好的名称有任何想法Run,请提出建议。:-)

¹这也ContinueWith带来了许多其他问题,例如在AggregateExceptions 中包装错误,很容易误吞异常,难以传播IsCanceled先行任务状态,使得泄漏即发即忘任务变得微不足道,需要由异步委托等创建的Unwrap嵌套Task<Task>s。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章