任务与线程差异
我不熟悉并行编程。 .NET中有两个类: Task
和Thread
。
所以,问题是:这些类之间有什么区别? 什么时候使用Thread
和Task
时更好?
Thread
是一个较低层次的概念:如果你直接启动一个线程,你就知道它将是一个单独的线程,而不是在线程池中执行。
Task
不仅仅是“在哪里运行某些代码”的抽象概念 – 它实际上只是“未来结果的承诺”。 举一些不同的例子:
-
Task.Delay
不需要任何实际的CPU时间; 就像设定一个定时器在未来会发生一样 - 由
WebClient.DownloadStringTaskAsync
返回的任务在本地占用的CPU时间不会太多; 它代表的结果可能会花费大部分时间在networking延迟或远程工作(在Web服务器上) -
Task.Run()
返回的任务确实是说“我希望你单独执行这个代码”; 该代码执行的确切线程取决于许多因素。
请注意, Task<T>
抽象是C#5中asynchronous支持的关键。
一般来说,我build议你尽可能使用更高层次的抽象:在现代C#代码中,你很less需要明确地启动你自己的线程。
资源
线
线程代表一个实际的操作系统级别的线程,具有自己的堆栈和内核资源。 (从技术上讲,CLR的实现可以使用光纤,但是现有的CLR没有这样做)。线程允许最高程度的控制; 你可以用Abort()或Suspend()或Resume()一个线程(虽然这是一个非常糟糕的主意),你可以观察它的状态,并且可以设置线程级属性,如堆栈大小,公寓状态或文化。
Thread的问题在于操作系统线程代价高昂。 每个线程都会为其堆栈消耗不less数量的内存,并随着处理器在线程之间进行切换而增加额外的CPU开销。 相反,当工作变得可用时,最好有一小块线程执行你的代码。
有些时候,没有其他的线程。 如果您需要指定名称(用于debugging目的)或公寓状态(显示一个用户界面),您必须创build自己的线程(请注意,有多个UI线程通常是一个坏主意)。 另外,如果你想维护一个线程所拥有的对象,并且只能被该线程使用,那么为它明确创build一个Thread实例就容易得多,所以你可以很容易地检查试图使用它的代码是否正在运行在正确的线程上。
线程池
ThreadPool是由CLR维护的线程池的一个包装。 ThreadPool完全不能控制; 你可以提交工作在某个时刻执行,你可以控制池的大小,但是你不能设置任何其他的东西。 您甚至无法分辨池何时开始运行您提交给它的工作。
使用ThreadPool避免了创build太multithreading的开销。 但是,如果向线程池提交太多长时间运行的任务,则它可能会变满,并且稍后提交的工作可能会最终等待较早的长时间运行的项目完成。 另外,ThreadPool没有办法找出工作项目何时完成(与Thread.Join()不同),也没有办法获得结果。 因此,ThreadPool最适合于调用者不需要结果的短操作。
任务
最后,任务并行库中的Task类提供了两全其美的方法。 像ThreadPool一样,任务不会创build自己的操作系统线程。 相反,任务由TaskScheduler执行; 默认调度器只是在ThreadPool上运行。
与ThreadPool不同的是,Task还允许您查找何时完成,以及(通过通用任务)返回结果。 您可以在现有任务上调用ContinueWith(),以便在任务完成后运行更多代码(如果已经完成,它将立即运行callback)。 如果任务是通用的,则ContinueWith()将传递给您任务的结果,允许您运行更多使用它的代码。
你也可以通过调用Wait()来同步等待任务完成(或者,对于通用任务,通过获取Result属性)。 像Thread.Join(),这将阻止调用线程,直到任务完成。 同步等待任务通常是个坏主意; 它阻止调用线程执行任何其他工作,并且如果任务最终等待(甚至asynchronous)当前线程,也可能导致死锁。
由于任务仍然在ThreadPool上运行,因此它们不应该用于长时间运行的操作,因为它们仍然可以填充线程池并阻止新的工作。 相反,Task提供了一个LongRunning选项,它将告诉TaskScheduler启动一个新的线程而不是在ThreadPool上运行。
所有较新的高级并发API,包括Parallel.For *()方法,PLINQ,C#5等待以及BCL中的现代asynchronous方法都是基于Task构build的。
结论
底线是任务几乎总是最好的select; 它提供了一个更强大的API,并避免浪费OS线程。
在现代代码中显式创build自己的线程的唯一原因是设置每线程选项,或者维护需要维护自己的身份的持久线程。
Thread
类用于在Windows中创build和操作线程 。
Task
表示一些asynchronous操作,并且是任务并行库的一部分, 任务并行库是用于asynchronous并行运行任务的一组API。
在旧的时代(即在TPL之前),曾经使用Thread
类是在后台或并行运行代码的一种标准方式(更好的select是经常使用ThreadPool
),但是这很麻烦并有几个缺点,其中最重要的是创build一个全新的线程在后台执行任务的性能开销。
现在使用任务和TPL是90%的时间更好的解决scheme,因为它提供了抽象,允许更有效地使用系统资源。 我想有几种情况下,你要显式控制你正在运行你的代码的线程,但是一般来说,如果你想asynchronous运行的东西,你的第一个端口应该是TPL。
任务是比线程更高层次的概念 …这就是这个短语的含义:
-
您不能使用Abort / ThreadAbortedException,您应该在您的“业务代码”中定期testing
token.IsCancellationRequested
标志(也避免长时间或无时间连接,例如db,否则您将永远没有机会testing此标志)支持取消事件。Thread.Sleep(delay)
应该被Task.Delay(delay, token);
replaceTask.Delay(delay, token);
-
任务中没有线程的Suspend()和Resume()方法function。 任务的实例也不能被重用。
-
但是你得到了两个新的工具: continuation和嵌套/子任务 ; 这两个样本展示了这个想法和语法:
// continuation - execute the delegate, when all tasks[] had been finished Task.Factory.ContinueWhenAll( tasks, () => { int answer = tasks[0].Result + tasks[1].Result; Console.WriteLine("The answer is {0}", answer); } ); //StartNew - starts task immediately, parent ends whith child var parent = Task.Factory.StartNew (() => { var child = Task.Factory.StartNew(() => { //... }); }, TaskCreationOptions.AttachedToParent );
-
所以线程完全隐藏任务,但任务的代码仍然在具体的系统线程中执行。 可以有不同的策略,线程池中的线程如何获得新的任务来执行。 TaskScheduler关心它。
TaskScheduler
解决的一些问题1)更喜欢在同一个线程中执行任务及其conitnuation最小化转换成本 – 也就是内联执行 )2)更喜欢执行任务的顺序,他们开始 – 也就是PreferFairness 3)更有效地分配任务之间的非活动线程取决于“先前的任务活动知识” – 又名偷工减料 。 重要提示:一般而言,“asynchronous”与“并行”不一样。 使用TaskScheduler选项可以设置asynchronous任务在一个线程中同步执行。 -
任务与C#async / await特性(也就是Promise Model)集成在一起,例如:
requestButton.Clicked += async (o, e) => ProcessResponce(await client.RequestAsync(e.ResourceName));
client.RequestAsync
的执行不会阻塞UI线程。 重要说明:引擎盖下的Clicked
委托调用是绝对正常的(所有线程都是由编译器完成的)。
这足以做出select。 如果您需要支持倾向于挂起的线程的取消function(例如,无时间连接),或者如果您正在创buildmultithreading后台计算,并且想要使用挂起/恢复来pipe理线程,则意味着要手动pipe理并行执行 – 留在线程中。 否则,去任务,因为他们会让你轻松操纵他们的群体,并融入语言。