aysnc/await vs. 任务/延续 UI 控件访问

本文关键字:UI 控件 访问 延续 任务 await vs aysnc | 更新日期: 2023-09-27 17:55:20

All,我有一个情况,我被要求多线程一个大的"成本紧缩"算法。我对Task相对有经验,并且有信心采用这样的模式

CancellationTokenSource cancelSource = new CancellationTokenSource();
CancellationToken token = cancelSource.Token;
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task<bool> asyncTask = null;
asyncTask = Task.Factory.StartNew<bool>(() =>
    SomeMethodAsync(uiScheduler, token, _dynamic), token);
asyncTask.ContinueWith(task =>
{
    // For call back, exception handling etc.
}, uiScheduler);

然后对于我需要提供的任何操作和 UI 操作,我会使用

Task task = Task.Factory.StartNew(() =>
{
    mainForm.progressLeftLabelText = _strProgressLabel;
}, CancellationToken.None, 
   TaskCreationOptions.None, 
   uiScheduler);

这可能包含在方法中。

现在,我意识到我可以使这一切变得不那么复杂,并利用 .NET 4.5 的async/await关键字。但是,我有一些问题:如果我有一个长时间运行的方法,我使用

// Start processing asynchroniously.
IProgress<CostEngine.ProgressInfo> progressIndicator =
    new Progress<CostEngine.ProgressInfo>();
cancelSource = new CancellationTokenSource();
CancellationToken token = cancelSource.Token;
CostEngine.ScriptProcessor script = new CostEngine.ScriptProcessor(this);
await script.ProcessScriptAsync(doc, progressIndicator, token);

其中CostEngine.ProgressInfo是用于返回进度信息的一些基本类,方法ProcessScriptAsync定义为

public async Task ProcessScriptAsync(SSGForm doc, IProgress<ProgressInfo> progressInfo,
                                     CancellationToken token, bool bShowCompleted = true)
{
    ...
    if (!await Task<bool>.Run(() => TheLongRunningProcess(doc)))
        return
    ...
}

我有两个问题:

  1. 为了让ProcessScriptAsync几乎立即将控制权返回给 UI,我等待一个新的Task<bool>委托(这似乎避免了无休止的 async/await 链)。这是称呼ProcessScriptAsync的正确方式吗?["延迟初始化",通过包装在外部方法中?

  2. 要从TheLongRunningProcess内部访问UI,我是否只需传入UI TaskScheduler uiScheduler;即 TheLongRunningProcess(doc, uiScheduler),然后使用:


Task task = Task.Factory.StartNew(() =>
{
    mainForm.progressLeftLabelText = _strProgressLabel;
}, CancellationToken.None, 
   TaskCreationOptions.None, 
   uiScheduler);

如故?

对不起,长度,谢谢你的时间。

aysnc/await vs. 任务/延续 UI 控件访问

  1. 这要看情况。 您已经展示了大量代码,但忽略了您实际提出的问题。 首先,如果不知道代码是什么,我们就无法知道它是否真的需要一段时间。 接下来,如果您等待已完成的任务,它将意识到这一点,而不是安排继续,而是继续(这是一种优化,因为计划任务很耗时)。 如果您等待的任务未完成,则继续仍将在调用SynchronizationContext中执行,这将再次使 UI 线程保持繁忙状态。 不过,您可以使用ConfigureAwait(false)来确保延续在线程池中运行。 这应该可以处理这两个问题。 请注意,通过执行此操作,您将无法再访问ProcessScriptAsync ...部分中的 UI 控件(无需执行任何特殊操作)。 另请注意,由于 ProcessScriptAsync 现在在线程池线程中执行,因此无需使用 Task.Run 将方法调用移动到后台线程。

  2. 这是一种选择,是的。 虽然,如果您根据进度更新 UI,这就是IProgress的用途。 我看到您已经在使用它,因此这是执行此操作的可取模型。 如果这是更新与您正在传递的现有IProgress不同的进度类型(即状态文本,而不是作为 int 的完成百分比),那么您可以传递第二个。

我认为尝试在后台线程(对于 CPU 密集型操作或不支持async的 IO 操作)和 UI 线程(操作 UI 控件)之间来回切换通常是设计不佳的标志。计算和 UI 代码应分开。

如果这样做只是为了通知 UI 某种进度,请使用 IProgress<T> 。然后,线程之间的任何封送处理都将成为该接口实现的责任,您可以使用 Progress<T> ,它使用 SynchronizationContext 正确执行此操作。

如果您无法避免混合后台线程代码和 UI 线程代码,并且您的 UI 工作不是进度报告(因此IProgress<T>不适合),我可能会将后台线程代码的每一位包含在它自己的await Task.Run()中,并将 UI 代码保留为顶层。

使用单个Task.Run()运行后台线程代码,然后使用StartNew()uiScheduler切换到 UI 线程的解决方案也可以使用。在这种情况下,一些帮助程序方法可能很有用,特别是如果你也想在 UI 代码中使用await。(否则,您必须记住将StartNew()的结果加倍await

另一种选择是创建一个SwitchTo(TaskScheduler)方法,该方法将返回一个自定义等待程序,该等待程序在给定的调度程序上继续。这种方法在某些异步 CTP 中存在,但由于在处理异常时被认为太危险而被删除。