【问题标题】:How to determine which thread a method will executes on when using TPL?使用 TPL 时如何确定方法将在哪个线程上执行?
【发布时间】:2013-04-07 14:29:57
【问题描述】:

我知道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 在不同的线程上?

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

【问题讨论】:

  • 您在控制台应用程序中使用异步 void 方法。真的可以吗?
  • @WouterdeKort 从输出中可以看出,它确实有效(因为ReadLine()。虽然这是一种不好的做法,不应该在生产代码中完成。
  • @WouterdeKort 它是“有效的”,因为主要是 ReadLine。如果 main 没有让应用程序像那样运行,那么您将永远无法在 async void 方法上“等待”,并且应用程序甚至可能在任务开始之前退出。不建议将 async void 方法用于事件处理程序以外的任何内容。
  • 您提出这个问题,并因此与工具抗争而不是利用它,这表明您需要重新考虑您的设计和/或您对工具的理解。将您的应用程序重新考虑为由(真实的物理)人员在办公室完成的工作,与实际的数据文件夹进行通信。当该工作流程有意义时,转换回自动化解决方案。

标签: c# parallel-processing task-parallel-library async-await c#-5.0


【解决方案1】:

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

首先,如果您的Tasks 由默认调度程序调度,则无法保证Thread 将在哪个Task 上运行。并且AwaitTest()的部分是分开执行的,所以不能保证它们会在同一个线程上运行。

其次,默认调度器使用ThreadPool来执行Tasks。并且每个async 方法的第一部分同步运行。在您的情况下,这意味着AwaitTest() 的第一部分将在主线程上运行,第二部分将在某个ThreadPool 线程上运行。因此,您实际上可以保证它们不会在同一个线程上运行。

当线程关联很重要时,这会导致问题吗?

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

但在其他情况下,它会导致问题。比如下面的代码:

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

此代码在控制台应用程序中不起作用(Exit() 很可能会抛出 SynchronizationLockException),因为 Exit() 可以在与 Enter() 不同的线程上运行。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-27
        • 1970-01-01
        • 2021-03-11
        • 2010-09-13
        • 1970-01-01
        相关资源
        最近更新 更多