多线程不处理所有任务

时间:2015-06-18 04:52:00

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

我使用下面的代码来执行使用线程的任务,这里我尝试执行“dtTable”Datatable中的所有记录。有限的线程数为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(仅供参考)。

2 个答案:

答案 0 :(得分:4)

问题是你在等待线程变为空闲时继续迭代数据表中的行。在任何情况下,这种多线程,即使它实际上是正确的(它不是),也是非常低效的 - 大部分时间都可能用于启动新线程。

尝试这样的事情:

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

修改

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

这是因为您的代码非常不安全且效率低下:

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

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

答案 1 :(得分:2)

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

Luann提出的解决方案很好,但我会使用Cast<DataRow>()而不是OfType<DataRow>()(因为枚举中的所有元素实际上都应该是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);
}

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

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

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