我的线程池出了什么问题

本文关键字:什么 问题 线程 我的 | 更新日期: 2023-09-27 18:29:37

我每天都要从远程web服务收集报告,我的代码如下:

    public static void ProcessEnMasse(System.DateTime fromDate, DateTime endDate)
    {
        System.Threading.ThreadPool.SetMaxThreads(10, 10);
        for (System.DateTime d = fromDate; d <= endDate; d = d.AddDays(1))
        {
            System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(day => ProcessOneDay(d)));
        }
    }
    public static void ProcessOneDay(System.DateTime theDate)
    {
        Log.Debug(string.Format("Processing {0:yyyy-MM-dd}...", theDate));
        var thePackager = new DataPackager();
        thePackager.CreateDatabaseImportPackage(theDate, theDate, true, false);
    }

当我查看日志时,我注意到几个线程正在处理相同的日期。为什么会这样,我需要做些什么来防止这种情况发生?

我的线程池出了什么问题

您需要注意闭包(我稍后会给出更全面的解释)

基本上,你的代码应该是这样的:

public static void ProcessEnMasse(System.DateTime fromDate, DateTime endDate)
    {
        System.Threading.ThreadPool.SetMaxThreads(10, 10);
        for (System.DateTime d = fromDate; d <= endDate; d = d.AddDays(1))
        {
            System.DateTime newD = d;
            System.Threading.ThreadPool.QueueUserWorkItem
                (new System.Threading.WaitCallback(day => ProcessOneDay(newD)));
        }
    }

以下是Jon Skeet关于闭包的章节

高级思想是,(在原始代码中)当您传入d时,它实际上会捕获该变量,编译后的代码会使该变量看起来像是一个全局共享变量。因此,当你进入下一个for步骤时,d不仅在你的循环中更新,而且在你刚刚传递给的函数中更新。

这是很高的水平,但我真的主张阅读Jon Skeet的文章,因为它写得很好。

如果您正在寻找更多信息,这里还有另一篇文章:)

所有lambda表达式共享相同的d变量。

如果其中一个工作项仅在初始线程运行d = d.AddDays(1)之后才开始,它将使用该日期。

要解决此问题,请在循环体中声明一个单独的局部变量,然后使用它。

您正在"捕获循环变量"。

解决方法:

for (System.DateTime d = fromDate; d <= endDate; d = d.AddDays(1))
{
     DateTime copy = d;
     System.Threading.ThreadPool.QueueUserWorkItem(
            new System.Threading.WaitCallback(day => ProcessOneDay(copy)));
}

或者你可以这样写(注意上面的代码中没有使用day):

for (System.DateTime d = fromDate; d <= endDate; d = d.AddDays(1))
{         
     ThreadPool.QueueUserWorkItem(day => ProcessOneDay((DateTime) day), d);
}