Jon*_*eet 408
Thread是一个较低级别的概念:如果你直接启动一个线程,你知道它将是一个单独的线程,而不是在线程池等上执行.
Task不仅仅是"在哪里运行代码"的抽象 - 它实际上只是"未来结果的承诺".所以作为一些不同的例子:
Task.Delay不需要任何实际的CPU时间; 这就像设置一个计时器在将来关闭WebClient.DownloadStringTaskAsync不会在本地占用太多CPU时间; 它代表的结果可能是大部分时间用于网络延迟或远程工作(在Web服务器上)Task.Run()真正返回的任务是说"我希望你单独执行这个代码"; 该代码执行的确切线程取决于许多因素.请注意,Task<T>抽象是C#5中异步支持的关键.
一般来说,我建议您尽可能使用更高级别的抽象:在现代C#代码中,您应该很少需要显式启动自己的线程.
M_ *_* Fa 288
线
Thread表示一个实际的OS级线程,具有自己的堆栈和内核资源.(从技术上讲,CLR实现可以使用光纤,但现有的CLR不会这样做)线程允许最高程度的控制; 你可以Abort()或Suspend()或Resume()一个线程(虽然这是一个非常糟糕的主意),你可以观察它的状态,你可以设置线程级属性,如堆栈大小,公寓状态或文化.
Thread的问题在于OS线程成本高昂.您拥有的每个线程都会为其堆栈消耗大量内存,并在线程之间切换处理器上下文时增加额外的CPU开销.相反,最好让一小部分线程在工作可用时执行代码.
有些时候没有替代线程.如果需要指定名称(用于调试目的)或单元状态(用于显示UI),则必须创建自己的线程(请注意,具有多个UI线程通常是个坏主意).此外,如果您想维护一个由单个线程拥有并且只能由该线程使用的对象,那么为它显式创建一个Thread实例要容易得多,这样您就可以轻松检查尝试使用它的代码是否正在运行在正确的线程上.
线程池
ThreadPool是CLR维护的线程池的包装器.ThreadPool完全没有控制权; 您可以提交工作以在某个时刻执行,并且您可以控制池的大小,但是您不能设置其他任何内容.您甚至无法判断池何时开始运行您提交给它的工作.
使用ThreadPool可以避免创建太多线程的开销.但是,如果向线程池提交太多长时间运行的任务,它可能会变满,以后您提交的工作最终可能会等待较早的长时间运行的项目完成.此外,ThreadPool无法找出工作项何时完成(与Thread.Join()不同),也无法获得结果.因此,ThreadPool最适用于调用者不需要结果的短操作.
任务
最后,任务并行库中的Task类提供了两全其美的功能.与ThreadPool一样,任务不会创建自己的OS线程.相反,任务由TaskScheduler执行; 默认调度程序只在ThreadPool上运行.
与ThreadPool不同,Task还允许您查明它何时完成,并(通过通用任务)返回结果.您可以在现有任务上调用ContinueWith(),以便在任务完成后运行更多代码(如果它已经完成,它将立即运行回调).如果任务是通用的,ContinueWith()将向您传递任务的结果,允许您运行更多使用它的代码.
您还可以通过调用Wait()来同步等待任务完成(或者,对于通用任务,通过获取Result属性).与Thread.Join()一样,这将阻止调用线程,直到任务完成.同步等待任务通常是个坏主意; 它可以防止调用线程执行任何其他工作,并且如果任务最终等待(甚至是异步)当前线程,也可能导致死锁.
由于任务仍然在ThreadPool上运行,因此它们不应该用于长时间运行的操作,因为它们仍然可以填满线程池并阻止新工作.相反,Task提供了一个LongRunning选项,它将告诉TaskScheduler启动一个新线程而不是在ThreadPool上运行.
所有较新的高级并发API,包括Parallel.For*()方法,PLINQ,C#5等待,以及BCL中的现代异步方法都是基于Task构建的.
结论
最重要的是,任务几乎总是最好的选择; 它提供了更强大的API,避免浪费操作系统线程.
在现代代码中显式创建自己的线程的唯一原因是设置每线程选项,或维护需要维护自己的标识的持久线程.
Rom*_*kij 36
通常你会听到Task是比线程更高级别的概念 ......这就是这句话的含义:
你不能使用Abort/ThreadAbortedException,你应该在你的"业务代码"中定期支持取消事件测试token.IsCancellationRequested标志(也避免长时间或无超时连接,例如db,否则你将永远不会有机会测试这个标志).由于类似的原因,Thread.Sleep(delay)呼叫应该用Task.Delay(delay, token)呼叫代替 .
任务没有线程Suspend和Resume方法功能.任务实例也无法重用.
但是你得到两个新工具:
a)延续
// 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);
}
);
Run Code Online (Sandbox Code Playgroud)
b)嵌套/子任务
//StartNew - starts task immediately, parent ends whith child
var parent = Task.Factory.StartNew
(() => {
var child = Task.Factory.StartNew(() =>
{
//...
});
},
TaskCreationOptions.AttachedToParent
);
Run Code Online (Sandbox Code Playgroud)因此系统线程完全隐藏在任务之外,但仍然在具体的系统线程中执行任务的代码.系统线程是任务和资源的资源,在任务并行执行的引擎下仍然存在线程池.线程如何让新任务执行有不同的策略.另一个共享资源TaskScheduler关心它.TaskScheduler解决的一些问题1)更喜欢执行任务及其在同一线程中的最小化,从而最大限度地降低转换成本 - 也就是内联执行)2)更喜欢按照启动顺序执行任务 - 也就是PreferFairness 3)在非活动线程之间更有效地分配任务取决于"任务活动的先验知识" - 又名工作窃取.重要提示:通常"异步"与"并行"不同.使用TaskScheduler选项,您可以设置异步任务同步在一个线程中执行.为了表达并行执行代码更高的抽象(比任务),可以使用:Parallel.ForEach,PLINQ,Dataflow.
任务与C#async/await功能也称为Promise Model集成,例如requestButton.Clicked += async (o, e) => ProcessResponce(await client.RequestAsync(e.ResourceName));执行client.RequestAsync不会阻止UI线程.重要:在引擎盖下Clicked委托调用是绝对常规的(所有线程都是由编译器完成的).
这足以做出选择.如果您需要支持调用遗留API的取消功能(例如无超连接),并且此情况支持Thread.Abort(),或者您正在创建多线程后台计算并希望使用Suspend/Resume优化线程之间的切换,这意味着手动管理并行执行 - 留在Thread.否则转到Tasks,因为它们可以让您轻松操作它们,将它们集成到语言中,使开发人员更高效地完成任务并行库(TPL).
Jus*_*tin 34
A Task代表一些异步操作,是Task Parallel Library的一部分,Task Parallel Library是一组用于异步和并行运行任务的API.
在旧的时代(即TPL之前)过去,使用Thread该类是在后台或并行运行代码的标准方法之一(更好的选择通常是使用a ThreadPool),但这很麻烦并且有一些缺点,尤其是创建一个全新线程以在后台执行任务的性能开销.
如今,使用任务和TPL在90%的时间内都是一个更好的解决方案,因为它提供了抽象,可以更有效地使用系统资源.我想有一些场景你需要显式控制运行代码的线程,但一般来说,如果你想异步运行某些东西,你的第一个调用端口应该是TPL.
| 归档时间: |
|
| 查看次数: |
244119 次 |
| 最近记录: |