【问题标题】:Multi Threading Not Processing all Task多线程不处理所有任务
【发布时间】:2015-09-03 13:54:31
【问题描述】:

我正在使用以下代码使用线程来执行任务,这里我尝试执行“dtTable”数据表中的所有记录。有限的线程数为 2(即有时只允许两个线程/只允许执行)。问题是它没有执行 Datatable 中可用的所有记录,它以不规则的方式执行数据。可能是什么问题..?提前致谢。

 public class Generator : IDisposable
 {
    public static int maxThreadCount = 2;
    public static int runningThreadCount = 0;

    public RunTask()
    {
       for (int ro = 0; ro <= dtTable.Rows.Count - 1; ro++)
       {
          if (maxThreadCount > runningThreadCount)
          {
            Thread atpthread = new Thread(delegate()
            {
            DoOperationMethod(dtTable.Rows[ro], Task, startDate, EndDate, dtTemplate);
            });
            atpthread.Start();
            runningThreadCount = runningThreadCount + 1;
            Mainthreads.Add(atpthread);
          }
          else
          {
            ro--;
          }
       }
  }

  public void DoOperationMethod(DataRow drAttachpoint, System.StrTaskItem Task, DateTime startDate, DateTime EndDate, DataTable dtTemplate)
    {
     //doing my Operation
     runningThreadCount = runningThreadCount-1; //Once Task done count will get reduce
    }

}

我正在使用 .net 3.5(仅供参考)。

【问题讨论】:

  • 你能把剩下的代码贴出来吗?它可能与 runningThreadCount 有关,您似乎在多个线程中同时更改,但我们无法确定该代码。
  • 看来DoOperationMethod 也可以直接与DataTable 一起使用,但由于DataTable 不是线程安全的,因此您可能会遇到问题。我们需要看代码来确认。
  • 感谢您的神秘,我已经添加了其余代码,

标签: c# multithreading for-loop parallel-processing


【解决方案1】:

问题是您在等待线程空闲时不断迭代数据表中的行。无论如何,这种多线程,即使它实际上是正确的(它不是),也是相当低效的——大部分时间可能都花在启动新线程上。

试试这样的:

Parallel
 .ForEach(dtTable.Rows.OfType<DataRow>(), row => DoOperationMethod(...))
 .WithDegreeOfParallelism(2);

编辑:

要弄清楚问题是如何产生的,您必须了解如何在匿名方法中捕获变量。您的DoOperationMethod 调用没有被传递到您想要的数据行,因为ro“变量”没有被复制,而是被引用。因此,当ro 在循环中发生变化时,它也会在您创建的线程中发生变化。

除了你的代码是可怕的线程不安全和低效的事实之外:

  • 实际上,您正在浪费 三个 线程来工作 - 没有阻塞,您的循环只是不断地加减 ro,这几乎完全是纯 CPU 工作。这比在等待结果时简单地阻塞更浪费。
  • 您不能只从多个线程读取和写入静态字段并期望事情正常工作。实际上,您的代码很容易并行启动比您想要的更多的线程 - 甚至在没有线程运行的情况下,runningThread 最终成为2 使整个事情陷入僵局。
  • 您不断启动新线程来执行看似微不足道的操作 - 我猜您的大部分工作要么受 I/O 限制,要么受制于一遍又一遍地创建新线程的成本。
  • 我假设 Mainthreads 是某种类型的列表,并且我假设您也从 DoOperationMethod 方法修改它 - 同样,这可能会导致随机异常和意外结果。
  • 理论上,由于各种优化和缓存,检查maxThreadCount &gt; runningThreadCount 甚至可能永远不会被评估。实际上,在当前 x86 CPU 上的 .NET 上,这种复杂的方法不太可能出现这种情况,但是当您更新到 .NET 7.0 或其他版本时,这种情况可能会咬到你 :)

多线程是困难的。你真的不想猜测你的方式。至少,尝试先了解基础知识 - http://www.albahari.com/threading/

【讨论】:

  • 您说 - “问题是您在等待线程空闲时不断迭代数据表中的行。”你能说说为什么会出现这个问题吗?
  • 当然。线程中代码的执行花费的时间比循环的两次迭代要长(否则首先使用多线程就没有什么意义了)。但是,这也意味着无需等待,在您“释放”一个线程(例如runningThreadCount-- 或类似的;是的,我知道它是可怕的线程不安全的)的时间内,您设法迭代了一些对 它们 不做任何操作的行——也就是说,它们被完全跳过了。
  • 抱歉,我可能有点厚,但我不明白这如何解释为什么
  • @Enigmativity:问题是ro 被匿名方法捕获,但它的值可以在线程实际运行之前改变。例如。前两个线程被创建并启动,但在循环将ro 增加到2 的值之前,它们实际上并没有获得CPU 时间。所以前两个线程处理行2,而不是行01。更糟糕的是,循环在等待线程完成时不断增加和减少ro
  • @PeterDuniho - 这就是问题所在。我们的朋友 Luaan 应该更新他们的答案以反映您的评论。
【解决方案2】:

在我看来,您的代码最大的问题是,即使您修复了 runningThreadCount 的线程不安全用法,您的代码在等待某个线程完成时也会“旋转”。当您尝试完成实际工作时,这会完全占用 CPU 内核。

Luann 提出的解决方案很好,尽管我会使用Cast&lt;DataRow&gt;() 而不是OfType&lt;DataRow&gt;()(因为枚举中的所有元素实际上都应该是DataRow 类型)。除了简洁之外,它的一大优点是它使用了线程池,这将大大减少线程管理的开销(因为它重用线程而不是一遍又一遍地创建和销毁它们)。

如果您更喜欢更明确的方法,您可以修改您发布的代码以使用信号量:

SemaphoreSlim semaphore = new SemaphoreSlim(2);

for (int ro = 0; ro <= dtTable.Rows.Count - 1; ro++)
{
    semaphore.Wait();

    DataRow row = dtTable.Rows[ro];

    Thread atpthread = new Thread(delegate()
    {
        DoOperationMethod(row, Task, startDate, EndDate, dtTemplate);
        semaphore.Release();
    });
    atpthread.Start();
    Mainthreads.Add(atpthread);
}

这将导致主线程在信号量计数达到 0 时阻塞 Wait() 调用,并在计数再次为正时(即在线程调用 Release() 之后)继续。

我注意到评论者 Enigmativity 关于dtTable 是否安全使用的观点。我在这里假设对象在此处理过程中没有被修改。有了这个假设,不同步使用它应该没问题。但如果这个假设是错误的,那么我同意他们的观点,那就是代码中的另一个错误。

最后,我要指出,您看到行被跳过的原因是您在匿名方法中使用了变量ro。在匿名方法开始执行循环的给定迭代之前,该变量可以很容易地递增,从而导致该线程处理错误的行。某些行可能会被处理多次,而其他行则被跳过。我已经通过在循环块内的变量中检索DataRow 对象来解决上述代码示例中的问题,以便每个线程都获得它自己的变量的私有副本,以及它应该处理的DataRow 对象.

【讨论】:

  • 嗨 Peter Duniho,“SemaphoreSlim”将在 .net 3.5 中支持?
  • 没有。我在您的问题中没有看到任何内容表明您坚持使用 3.5,但如果您是,您可以使用 Semaphore 代替。工作原理完全相同(我的意思是,语法略有不同,但行为和功能是相同的)。
  • DataRowCollection 中的任何内容 DataRow 时,为什么要Cast?我使用OfType 的唯一原因是假装它实现了IEnumerable&lt;DataRow&gt; :))
  • @Luaan: Cast&lt;T&gt;() 等价于强制转换(当然),而OfType&lt;T&gt;() 等价于as 后跟一个空检查。我使用Cast&lt;T&gt;() 而不是OfType&lt;T&gt;() 的原因与我使用实际演员而不是as 的原因相同;对于手头的操作,它在语义上是正确的。 IE。如果我有错误,将抛出 InvalidCastException() 而不是 NullReferenceException。使用OfType&lt;T&gt;() 更糟糕,因为它可能会默默地失败(即只是不返回对象)。当然,这并不重要,但好的习惯会带来好的代码。
猜你喜欢
  • 1970-01-01
  • 2012-12-05
  • 2021-11-09
  • 1970-01-01
  • 1970-01-01
  • 2015-12-29
  • 1970-01-01
  • 2022-01-18
  • 1970-01-01
相关资源
最近更新 更多