在.NET中有两个可用的类:Task
和Thread
。
- 这些类之间有什么区别?
- 何时更好地使用
Thread
而不是Task
(反之亦然)?
在.NET中有两个可用的类:Task
和Thread
。
Thread
而不是Task
(反之亦然)?Thread
是一个较低级概念:如果你直接启动一个线程,你知道它将是一个单独的线程,而不是在线程池等其他地方执行。
Task
不仅仅是“在哪里运行一些代码”的抽象 - 它实际上只是“未来结果的承诺”。因此,以下是一些不同的例子:
Task.Delay
不需要任何实际的 CPU 时间;它就像在未来设置一个定时器。WebClient.DownloadStringTaskAsync
返回的任务在本地不会占用太多 CPU 时间;它代表一个结果,该结果可能会在网络延迟或远程工作(在 web 服务器上)中花费大部分时间。Task.Run()
返回的任务确实在说“我想让你单独执行这段代码”;该代码执行的确切线程取决于多种因素。请注意,Task<T>
抽象是 C# 5 中异步支持的关键。
总的来说,我建议您在尽可能的情况下使用更高级别的抽象:在现代的 C# 代码中,您很少需要显式地启动自己的线程。
new Thread()
不处理线程池线程,而 Task
则使用线程池线程。 - Royi Namir通常听到的是任务比线程更高级...这句话的意思是:
你不能使用 Abort/ThreadAbortedException,在你的“业务代码”中应该周期性地支持cancel event,测试 token.IsCancellationRequested
标志(还要避免长时间或无超时的连接,例如到数据库,否则你将永远没有机会测试此标志)。由于类似的原因,Thread.Sleep(delay)
调用应该被替换为 Task.Delay(delay, token)
调用(传递 token 内部以便能够中断延迟)。
任务没有线程的 Suspend
和 Resume
方法功能。也不能重复使用任务实例。
但你有两个新工具:
a) continuations (续体)
// continuation with ContinueWhenAll - execute the delegate, when ALL
// tasks[] had been finished; other option is ContinueWhenAny
Task.Factory.ContinueWhenAll(
tasks,
() => {
int answer = tasks[0].Result + tasks[1].Result;
Console.WriteLine("The answer is {0}", answer);
}
);
b) 嵌套/子任务 //StartNew - starts task immediately, parent ends whith child
var parent = Task.Factory.StartNew
(() => {
var child = Task.Factory.StartNew(() =>
{
//...
});
},
TaskCreationOptions.AttachedToParent
);
系统线程完全隐藏在任务之外,但任务的代码仍在具体的系统线程中执行。系统线程是任务的资源,当然,在任务的并行执行下,线程池仍然存在。可以有不同的策略来获取新的任务以执行。另一个共享资源TaskScheduler会关心这个问题。一些TaskScheduler解决的问题:1)优先在同一线程中执行任务及其继续,从而最小化切换成本--即内联执行 2)按启动顺序执行任务--也就是 PreferFairness 3)根据“任务活动的先前知识”更有效地分配任务到闲置线程之间--即Work Stealing 。重要提示:一般来说,“async”不等同于“parallel”。通过操作TaskScheduler选项,可以将异步任务设置为在一个线程中同步执行。为了表达并行代码执行,可以使用比Tasks更高级的抽象:Parallel.ForEach
,PLINQ
,Dataflow
。
任务与C#的异步/等待特性即Promise模型集成在一起,例如requestButton.Clicked += async (o, e) => ProcessResponce(await client.RequestAsync(e.ResourceName));
执行client.RequestAsync
的时候将不会阻塞UI线程。重要提示:在幕后,Clicked
委托调用是绝对常规的(所有线程都由编译器完成)。
以上就足以做出选择了。如果需要支持调用传统API的取消功能(例如没有超时连接),并且为此目的支持Thread.Abort(),或者如果您正在创建多线程后台计算并希望通过Suspend/Resume优化线程之间的切换,则意味着手动管理并行执行--请使用Thread。否则,请使用Tasks,因为它们将使您轻松操作它们的组,已经集成到语言中,并使开发人员更具生产力-任务并行库(TPL)。
Thread.Abort
对于大多数长时间操作,如数据库连接等,并不起作用。它只能在托管代码中进行中止,而大多数长时间等待的操作都被卡在本地代码中(例如等待句柄、I/O 操作等)。唯一的好处是,它可以相对安全地在托管代码中的任何位置中止(例如不在 finally
子句中等),因此它可以防止出现无限循环等错误。但在生产级别的代码中使用它并没有太多意义。 - LuaanThread.Abort
是有害的,应尽可能避免使用。在可能发生线程中止的情况下运行的代码极难正确推理。这不值得,只需检查某种标志即可。(我建议使用 CancellationToken
API,即使您没有使用 Tasks)。 - JorenTask.Delay(delay, token);
替换Thread.Sleep(delay)
。如果需要,则应该用Task.Delay(delay, token).Wait();
替换,它与Thread.Sleep(delay)
相同,或者如果可能的话,用await Task.Delay(delay, token)
替换。 - RekshinoTask
表示某些异步操作的一部分,并且是任务并行库(TPL)的一部分,这是一组API,可用于异步运行和并行运行任务。
早期(即TPL之前),使用Thread
类是在后台或并行运行代码的标准方式之一(更好的替代方案通常是使用ThreadPool
),但这很麻烦,并且有几个缺点,其中最大的是创建一个全新线程来执行后台任务的性能开销。
现在使用任务和TPL通常是更好的解决方案,因为它提供了抽象,使得系统资源的使用更加高效。我想你可能会遇到一些情况,需要显式控制正在运行代码的线程,但一般来说,如果想要异步运行某些内容,应首先考虑TPL。