为什么继续似乎按顺序阻止和/或运行任务

本文关键字:运行 任务 继续 顺序 为什么 | 更新日期: 2023-09-27 18:32:55

我对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 有关。有趣的是,如果我设置选项 TaskContinuationOptions.LongRun on ContinueWith 它异步运行任务而不会阻塞,我也在我的生产应用程序中完成了此操作,这已经解决了这个问题。

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

任何帮助或建议将不胜感激。

更新

另外有趣的是,如果我更换

    SomeLongRunningDatabaseCall(10001) 

用类似的东西

    Thread.Sleep(600000) 

但是,它不会通过调用数据库来阻塞,但由于它在自己的线程中运行,因此至少其他任务不应该有任何阻塞,因为任务> 50 不会调用数据库。

为什么继续似乎按顺序阻止和/或运行任务

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

在这种情况下,制作 Parallel.ForEach(( 可能最终会让你的应用程序感到无聊,因为数据库连接(相对(昂贵,而且打开一大堆数据库连接实际上是不可行的。最好使用不同的模式,例如将item对象放入ConcurrentQueueConcurrentStack中,并针对此集合抛出 3-5 个线程(或更多 - 您必须在此处进行实验(并让这些线程运行直到堆栈或队列耗尽。这样,您可以将实例化数据库连接的数量保持在安全数字,并且仍然可以避免这种令人讨厌的阻塞。

所有这些使我认为使用Parallel.ForEach进行实际的数据库执行(这里不谈论 LINQ-to-SQL 或 EF,而是实际的数据库连接调用,如 SqlCommand.Execute(可能是次优的。

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