有人能解释一下使用ThreadPool时的这种奇怪行为吗

本文关键字:ThreadPool 能解释 一下 | 更新日期: 2023-09-27 18:29:55

代码

using System;
using System.Threading;
public delegate void LoadingProgressCallback(double PercentComplete,string ItemName);
public delegate void LoadCompleteCallback(int ItemID, string ItemName);
public static class Program
{
    public static void Main(string[] args)
    {
        LoadTest loadTest = new LoadTest();
        loadTest.LoadItems(args);
    }
}
public class LoadTest
{       
    ManualResetEvent resetEvent;
    int numThreads = 0;
    public LoadTest()
    {}
    public void LoadItems(string[] Items)
    {
        numThreads = 0;
        resetEvent = new ManualResetEvent(false);
        foreach(string item in Items)
        {
            Console.WriteLine("Adding {0} to ThreadPool",item);
            ThreadPool.QueueUserWorkItem
            (
                delegate
                {
                    Load(item, this.progCall, this.compCall);
                }
            );
            numThreads++;
            Thread.Sleep(100);//Remove this line
        }
        resetEvent.WaitOne();
    }
    public void progCall(double PercentComplete, string ItemName)
    {
        Console.WriteLine("{0}: is {1}% Complete [THREAD:{2}]",ItemName,PercentComplete.ToString(),Thread.CurrentThread.ManagedThreadId.ToString());
    }
    public void compCall(int ItemID, string ItemName)
    {
        Console.WriteLine("{0}: is Complete",ItemName);
        numThreads--;
        if(numThreads == 0)
        {
            resetEvent.Set();
        }
    }
    public void Load(string Item, LoadingProgressCallback progressCallback, LoadCompleteCallback completeCallback)
    {
        Console.WriteLine("Loading: {0} [THREAD:{1}]",Item,Thread.CurrentThread.ManagedThreadId.ToString());
        for(int i = 0; i <= 100; i++)
        {
            if(progressCallback != null)
            {
                progressCallback((double)i, Item);
            }
            Thread.Sleep(100);
        }
        if(completeCallback != null)
        {
            completeCallback(0,Item);
        }
    }
}

观察

如果我从命令行运行这个程序,就像这样。。。

>TheProgram item1 item2

输出将如下所示。

正在将项目1添加到线程池
加载:项目1[THREAD:3]
项目1:为0%完成[THREAD:3]
正在将项目2添加到线程池
加载:项目2[THREAD:4]
项目2:已完成0%[THREAD:4]
项目1:已完成1%[THREAD:3]
项目2:已完成1%[THREAD:4]
项目1:已完成2%[THREAD:3]
项目2:完成2%[线程:4]

但是,如果我删除这行。

Thread.Sleep(100);//Remove this line

LoadItems方法中,输出如下所示。

正在将项目1添加到线程池
正在将项目2添加到线程池
加载:项目2[THREAD:4]
加载:项目2[THREAD:3]
项目2:已完成0%[THREAD:4]
项目2:已完成0%[THREAD:3]
项目2:已完成1%[THREAD:4]
项目2:已完成1%[THREAD:3]
项目2:已完成2%[THREAD:3]
项目2:完成2%[线程:4]

问题

看起来好像使用了两个线程,尽管它们似乎都在对相同的数据执行操作。为什么代码会以这种方式运行?

有人能解释一下使用ThreadPool时的这种奇怪行为吗

您正在关闭循环变量,这会给您带来意外的结果。试试这个:

foreach(string item in Items)
{
    string item2 = item;
    Console.WriteLine("Adding {0} to ThreadPool", item2);
    ThreadPool.QueueUserWorkItem
    (
        delegate
        {
            Load(item2, this.progCall, this.compCall);
        }
    );
    numThreads++;
    Thread.Sleep(100);//Remove this line
}

参考

  • 闭合C中的循环变量#
  • 关闭被认为有害的循环变量

查看代码时会立即想到一件事,那就是没有使用Interlocked

你必须使用它,否则你会看到奇怪的错误和行为。

所以不是

numThreads++;

用途:

Interlocked.Increment(ref numThreads);