3

我知道TPL是面向任务的,而传统的线程模型是面向工作者的。 任务让你主要关注你想要解决什么问题,而不是如何使用 来完成。但是当涉及到线程和任务关系时,我仍然有点困惑。如何确定使用TPL时将执行哪个线程?

下面是一个演示代码:

namespace AsyncUnderTheHood 
{ 
    class Program 
    { 

     static void Main(string[] args) 
     { 
      Console.WriteLine("Main Start : {0}", Thread.CurrentThread.ManagedThreadId); 
      AwaitTest(); 
      Console.WriteLine("Main End : {0}", Thread.CurrentThread.ManagedThreadId); 
      Console.ReadLine(); 
     } 

     public static void DoWork() 
     { 
      Console.WriteLine("DoWork Start: {0}", Thread.CurrentThread.ManagedThreadId); 
      Thread.Sleep(5000); 
      Console.WriteLine("DoWork End: {0}", Thread.CurrentThread.ManagedThreadId); 
     } 

     public async static void AwaitTest() 
     { 
      Console.WriteLine("AwaitTest Start : {0}", Thread.CurrentThread.ManagedThreadId); 
      Task t = new Task(DoWork); 
      t.Start(); 
      await t; 
      Console.WriteLine("AwaitTest Done : {0}", Thread.CurrentThread.ManagedThreadId); 
     } 
    } 
} 

输出是这样的:

Main Start : 1 
AwaitTest Start : 1 <------------ A 
DoWork Start: 3 
Main End : 1 
DoWork End: 3 
AwaitTest Done : 3 <------------ B 

我的问题是,为什么A和B是在不同的线程?

相同的方法在不同的线程上执行,当线程亲和性很重要时,这会导致问题吗?

+0

您正在控制台应用程序中使用异步无效方法..是真的好吗? – 2013-04-07 16:33:16

+0

@WouterdeKort正如你从输出中看到的那样,它可以工作(因为'ReadLine()'。虽然这是一种不好的做法,不应该在生产代码中完成。 – svick 2013-04-07 16:35:04

+0

@WouterdeKort这是“工作”,因为如果main没有让应用程序像这样运行,你永远无法“等待”async void方法,应用程序可能在任务开始之前退出,不建议使用async void事件处理函数以外的任何其他方法 – 2013-04-07 16:37:19

回答

1

您已要求系统“等待”任务。你真正要求的是,调用await的线程应该继续运行,并且await之后的所有内容都是一个“延续”,当任务完成时它将异步运行。由于在控制台应用程序中没有“消息泵”,所以没有简单的方法可以重新回到“主”线程,因此继续使用异步Task的线程继续。如果您在WinForm或WPF应用程序中执行了相同的测试,则继续将在UI线程上运行。

3

为什么A和B在不同的线程?

首先,如果你Task S被默认的调度计划,那么就无法保证其Thread将在Task运行。并且AwaitTest()的各个部分分开执行,因此不能保证它们将在同一个线程上运行。

其次,默认调度程序使用ThreadPool来执行Task s。每个async方法的第一部分同步运行。在你的情况下,这意味着AwaitTest()的第一部分将在主线程上运行,而第二部分将在某个线程上运行ThreadPool线程。所以,你实际上保证他们不会在同一个线程上运行。

当线程亲和性很重要时,这会导致问题吗?

它当然可以。但是,在线程亲和性很重要的最常见情况下,它会正常工作:GUI编程。这是因为GUI应用程序具有SynchronizationContext集合,这意味着如果async方法的第一部分在UI线程上运行,则第二部分也将在那里运行(除非通过使用ConfigureAwait(false)来禁用该方法)。

但在其他情况下,它会导致问题。例如,采取以下代码:

Monitor.Enter(lockObject); 
await someTask; 
Monitor.Exit(lockObject); 

此代码不会在控制台应用程序中工作(Exit()将最有可能抛SynchronizationLockException),因为Exit()可以在不同的线程比Enter()运行。

0

This blog post对TPL如何使用多个任务队列进行工作窃取以在现有线程池之上进行分层以获得最佳性能有很好的描述。

相关问题