2

总而言之,我有一种情况,我被要求对一个大型的“成本计算”算法进行多线程处理。我对Tasks 比较有经验,并且有信心采用类似的模式

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);

这可能包含在一个方法中。

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

// 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 ,我等待一个新的委托(这似乎避免了无休止的/ s链)。这是正确的打电话方式吗?['延迟初始化',通过包装外部方法?]Task<bool>asyncawaitProcessScriptAsync

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


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

像之前一样?

对不起,长度和感谢您的时间。

4

2 回答 2

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

  2. 这是一种选择,是的。虽然,如果您根据进度更新 UI,这就是IProgress目的。我看到你已经在使用它了,所以这是最好的模型。如果这是在更新与您正在传递的现有进度不同类型的进度IProgress(即状态文本,而不是作为 int 的完成百分比),那么您可以传递一秒钟。

于 2013-01-22T18:45:43.817 回答
1

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

如果您这样做只是为了通知 UI 某种进展,请使用IProgress<T>. 然后线程之间的任何编组将成为该接口实现的责任,您可以Progress<T>使用SynchronizationContext.

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

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

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

于 2013-01-22T21:11:27.037 回答