所有,我有一种情况,我被要求多线程一个大的'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
...
}
我有两个问题:
要让ProcessScriptAsync
立即将控制权返回给用户界面 ,我等待一个新的Task<bool>
委托(这似乎避免了{{1 }} / async
S)。 这是通过外包方法调用await
? ['懒惰初始化'的正确方法吗?]
要从ProcessScriptAsync
内访问用户界面,我是否只需传入用户界面TheLongRunningProcess
TaskScheduler
;即uiScheduler
,然后使用:
TheLongRunningProcess(doc, uiScheduler)
和以前一样?
对此感到抱歉,感谢您的时间。
答案 0 :(得分:2)
这取决于。你已经展示了很多代码,但却忽略了你实际上问的一个问题。首先,在不知道代码是什么的情况下,我们无法知道它是否真的需要一段时间。接下来,如果等待已经完成的任务,它将实现这一点,而不是安排继续,而是继续(这是一个优化,因为调度任务很耗时)。如果您等待的任务未完成,则继续仍将在调用SynchronizationContext
中执行,这将再次使UI线程保持忙碌状态。您可以使用ConfigureAwait(false)
来确保延续在线程池中运行。这应该处理这两个问题。请注意,通过执行此操作,您无法再访问...
的{{1}}部分中的UI控件(无需执行任何特殊操作)。另请注意,由于ProcessScriptAsync
现在正在线程池线程中执行,因此您无需使用ProcessScriptAsync
将方法调用移至后台线程。
这是一个选择,是的。虽然,如果您根据进度更新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而被删除。