为什么ContinueWith似乎是按顺序阻塞和/或运行任务

时间:2014-12-03 18:12:18

标签: c# multithreading task-parallel-library

我对ContinueWith的工作方式感到困惑,它似乎阻止了ThreadPool并按顺序运行任务。以下我编写的代码示例:

         var items = new List<int>();

         for (int i = 0; i < 100; i++)
         {
             items.Add(i);
         }

         Parallel.ForEach(items, item =>
             {
               Task.Factory.StartNew(() =>
                 {

                 }).ContinueWith(t =>
                 {
                     if (item < 50)
                     {
                         System.Console.WriteLine("Blocking in {0}", item.ToString());
                         var x = SomeLongRunningDatabaseCall(10001);
                     }
                     System.Console.WriteLine("Item {0}", item);
                 });
             });

代码的目的是反映我在生产应用程序上遇到的可疑线程阻塞问题。令我惊讶的是上面的代码我发现问题似乎与我使用ContinueWith有关。有趣的是,如果我在ContinueWith上设置选项TaskContinuationOptions.LongRunning,它会异步运行任务而不会阻塞,我也在我的生产应用程序中完成了这个,这解决了这个问题。

然而,我真的很困惑,并希望更好地理解为什么没有选项TaskContinuationOptions.LongRunning的ContinueWith语句导致任务阻塞或似乎按顺序运行,即使我每次调用一个新线程迭代的项目。我唯一能想到的是,ContinueWith没有在先前任务执行的线程中运行,而是在可能导致阻塞的主线程上运行。

非常感谢任何帮助或建议。

更新

如果我更换

,还有什么有趣的
    SomeLongRunningDatabaseCall(10001) 

类似

    Thread.Sleep(600000) 
但是,它并没有阻止它对数据库的调用,但由于它在自己的线程中运行,因此至少应该阻止其他任务,因为任务&gt; 50不要打电话给数据库。

1 个答案:

答案 0 :(得分:0)

每个线程都应该使用它自己的数据库连接对象。我所知道的大多数数据库连接都不支持非锁定多线程模式。在这些数据库连接中,线程被阻塞并不罕见,因为单个连接将按顺序而不是并行执行调用。

在这种情况下,制作一个Parallel.ForEach()可能会最终导致你的应用程序失败,因为数据库连接(相对)昂贵,并且真的,打开一大堆是不可行的数据库连接。使用不同的模式可能会更好,例如将item个对象放入ConcurrentQueueConcurrentStack并投掷3-5个线程(或更多 - 您将拥有在这里进行实验)对这个集合进行测试并让它们运行直到堆栈或队列耗尽。这样,您可以将实例化数据库连接的数量保持为安全数字,并且仍然可以避免这种令人讨厌的阻塞。

所有这些让我觉得使用Parallel.ForEach来实际执行数据库(这里不讨论LINQ-to-SQL或EF,但实际的数据库连接调用,如SqlCommand.Execute)可能是sub - 最优。

编辑:您不必使用旧语法来表达我的建议。一堆任务就可以做得很好。