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
...
}
我有两个问题:
为了让
ProcessScriptAsync
几乎立即将控制权返回给 UI,我等待一个新的Task<bool>
委托(这似乎避免了无休止的async
/await
链)。这是称呼ProcessScriptAsync
的正确方式吗?["延迟初始化",通过包装在外部方法中?要从
TheLongRunningProcess
内部访问UI,我是否只需传入UITaskScheduler
uiScheduler
;即TheLongRunningProcess(doc, uiScheduler)
,然后使用:
Task task = Task.Factory.StartNew(() =>
{
mainForm.progressLeftLabelText = _strProgressLabel;
}, CancellationToken.None,
TaskCreationOptions.None,
uiScheduler);
如故?
对不起,长度,谢谢你的时间。
-
这要看情况。 您已经展示了大量代码,但忽略了您实际提出的问题。 首先,如果不知道代码是什么,我们就无法知道它是否真的需要一段时间。 接下来,如果您等待已完成的任务,它将意识到这一点,而不是安排继续,而是继续(这是一种优化,因为计划任务很耗时)。 如果您等待的任务未完成,则继续仍将在调用
SynchronizationContext
中执行,这将再次使 UI 线程保持繁忙状态。 不过,您可以使用ConfigureAwait(false)
来确保延续在线程池中运行。 这应该可以处理这两个问题。 请注意,通过执行此操作,您将无法再访问ProcessScriptAsync
...
部分中的 UI 控件(无需执行任何特殊操作)。 另请注意,由于ProcessScriptAsync
现在在线程池线程中执行,因此无需使用Task.Run
将方法调用移动到后台线程。 -
这是一种选择,是的。 虽然,如果您根据进度更新 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 中存在,但由于在处理异常时被认为太危险而被删除。