异步/等待Vs.任务/继续UI控制访问

时间:2013-01-22 18:19:07

标签: c# task .net-4.5 async-await

所有,我有一种情况,我被要求多线程一个大的'Cost-Crunching'算法。我对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立即将控制权返回给用户界面 ,我等待一个新的Task<bool>委托(这似乎避免了{{1 }} / async S)。 这是通过外包方法调用await ['懒惰初始化'的正确方法吗?]

  2. 要从ProcessScriptAsync内访问用户界面,我是否只需传入用户界面TheLongRunningProcess TaskScheduler;即uiScheduler,然后使用:


  3. TheLongRunningProcess(doc, uiScheduler)
    和以前一样?

    对此感到抱歉,感谢您的时间。

2 个答案:

答案 0 :(得分:2)

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

  2. 这是一个选择,是的。虽然,如果您根据进度更新UI,那就是Task.Run的用途。我看到你已经在使用它了,所以这是做这个的首选模型。如果这是更新一个单独的进度类型而不是您传递的现有IProgress(即状态 text ,而不是作为int完成的百分比),那么您可以传递一秒。 / p>

答案 1 :(得分:1)

我认为尝试在后台线程(用于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。 (否则,您必须记住将await)的结果加倍StartNew()

另一种选择是创建一个SwitchTo(TaskScheduler)方法,该方法将返回在给定调度程序上继续的自定义awaiter。这种方法在某些异步CTP中,但由于它是deemed too dangerous when it comes to handling exceptions而被删除。