从 .NET 4.0 开始,就有了执行异步任务的 TPL。如果您正在阅读 msdn,所有与表单/UI 交互的异步操作仍然使用 InvokeRequire ... Invoke() 模式。我要问的是这有原因吗?据我所知,TPL 应该是旧线程机制的替代品。那么在涉及 UI 线程时忽略它有什么意义呢?对此有什么想法吗?
4 回答
这似乎相当主观......
当您说“自 .NET 4.0 以来”时,您是在说“截至今年 4 月”-.net 已经存在 10 年了,并且 InvokeRequired/Invoke 已用于过去 9 年。为什么 MS 会破坏所有现有的出于任何原因的 UI 代码?即使存在调用线程的新方法,他们也不能简单地修改模式而没有巨大的兼容性问题。
此外,TPL 与 InvokeRequired/Invoke 不同——TPL 是关于简单的并行性,invoke 是关于在特定线程上运行代码。我不确定即使没有兼容性问题,为什么要替换另一个。
请注意,没有什么可以阻止您使用 TPL 来确保您在正确的线程上调用 UI 组件。事实上,您可以轻松做到这一点。但这取决于您,当前的 API 不会以不向后兼容的方式发生变化。
使用 TPL,您可以使用TaskScheduler.FromCurrentSynchronizationContext指定目标线程,此方法指定 Task 将在主线程上执行。我建议使用它而不是 Invoke。
这里的问题是什么?TPL 的存在并没有改变 UI 本质上是单线程的事实,并且要求控件只能在 UI 线程上访问。(这是 Windows 的限制,而不是 .NET UI 框架的限制。TPL 无法改变数十年的 Windows 设计限制。)
如果您的问题是关于将任务与 InvokeRequired/Invoke 混合使用,则有一种比 Invoke 更面向 TPL 的方式。TPL 提供了一种内置方式来安排延续任务在 UI 线程上运行。因此,您将面向背景的工作放在一个任务中,然后您的 UI 更新在另一个任务中。请参阅有关任务调度程序和 SynchronizationContext 的这篇文章。
(但实际上,TPL 并没有取代任何旧的线程 API。如果 Invoke 是完成您想做的事情的最佳方式,请使用它。)
private void WorkProcessingAsync(IWorkItem workItem)
{
IsBusy = true;
/* =============================
* Create a TPL Task and pass the current UiCulture in an state Object to resolve the correct .resx file for translation / globalisation / Multilanguate features in Background Thread
* ==============================*/
Task<IWorkItem> task = Task.Factory.StartNew((stateObj) =>
{
// here we are already in the task background thread
// save cast the given stateObj
var tuple = stateObj as Tuple<IWorkItem, CultureInfo>;
Debug.Assert(tuple != null, "tuple != null");
Thread.CurrentThread.CurrentUICulture = tuple.Item2; // Here we set the UI-Thread Culture to the Background Thread
var longRunningOperationAnswer = LongRunningOperation.DoLongWork(tuple.Item1);
return longRunningOperationAnswer;
}, new Tuple<IWorkItem, CultureInfo>(workItem, Thread.CurrentThread.CurrentUICulture)); // here we pass the UI-Thread Culture to the State Object
/* =======================================================================
* Handle OnlyOnRanToCompletion Task and process longRunningOperationAnswer back in UiThread
* =======================================================================*/
task.ContinueWith((t) =>
{
IsBusy = false;
// handle longRunningOperationAnswer here in t.Result
Log.Debug("Operation completet with {0}", t.Result);
}, CancellationToken.None
, TaskContinuationOptions.OnlyOnRanToCompletion
, TaskScheduler.FromCurrentSynchronizationContext());
/* =======================================================================
* Handle OnlyOnFaulted Task back in UiThread
* =======================================================================*/
task.ContinueWith((t) =>
{
IsBusy = false;
AggregateException aggEx = t.Exception;
if (aggEx != null)
{
aggEx.Flatten();
Log.ErrorFormat("The Task exited with Exception(s) \n{0}", aggEx);
foreach (Exception ex in aggEx.InnerExceptions)
{
if (ex is SpecialExaption)
{
//Handle Ex here
return;
}
if (ex is CustomExeption)
{
//Handle Ex here
return;
}
}
}
}, CancellationToken.None
, TaskContinuationOptions.OnlyOnFaulted
, TaskScheduler.FromCurrentSynchronizationContext());
}