我一直在学习如何使用线程池,但我不确定池中的每个线程是否都正确执行,并且我怀疑有些线程被执行多次。我已将代码削减到最低限度,并一直在使用 Debug.WriteLine 来尝试弄清楚发生了什么,但这会产生一些奇怪的结果。
我的代码如下(基于( WaitAll for multiple handles on a STA thread is not supported )的代码:
public void ThreadCheck()
{
string[] files;
classImport Import;
CountdownEvent done = new CountdownEvent(1);
ManualResetEvent[] doneEvents = new ManualResetEvent[10];
try
{
files = Directory.GetFiles(importDirectory, "*.ZIP");
for (int j = 0; j < doneEvents.Length; j++)
{
done.AddCount();
Import = new classImport(j, files[j], workingDirectory + @"\" + j.ToString(), doneEvents[j]);
ThreadPool.QueueUserWorkItem(
(state) =>
{
try
{
Import.ThreadPoolCallBack(state);
Debug.WriteLine("Thread " + j.ToString() + " started");
}
finally
{
done.Signal();
}
}, j);
}
done.Signal();
done.Wait();
}
catch (Exception ex)
{
Debug.WriteLine("Error in ThreadCheck():\n" + ex.ToString());
}
}
classImport.ThreadPoolCallBack 目前实际上没有执行任何操作。
如果我手动单步执行代码,我会得到:
线程 1 已启动 线程 2 已启动 ....一路到.... 线程 10 已启动
但是,如果我手动运行它,输出窗口将填充“线程 10 已启动”
我的问题是:我使用线程池的代码是否有问题,或者 Debug.WriteLine 的结果是否被多个线程混淆了?
最佳答案
问题在于您在 lambda 表达式中使用了循环变量 (j
)。
为什么这是一个问题的细节相当冗长 - 请参阅 Eric Lippert's blog post了解详细信息(另请阅读 part 2 )。
幸运的是,解决方法很简单:只需在循环内创建一个新的局部变量,并在 lambda 表达式中使用它:
for (int j = 0; j < doneEvents.Length; j++)
{
int localCopyOfJ = j;
... use localCopyOfJ within the lambda ...
}
对于循环体的其余部分,只使用 j
就可以了 - 只有当它被 lambda 表达式或匿名方法捕获时,它才会成为问题。
这是一个困扰很多人的常见问题 - C# 团队已考虑更改 foreach
循环的行为(其中确实看起来像您已经在每次迭代中声明了一个单独的变量),但这会导致有趣的兼容性问题。 (例如,您可以编写运行良好的 C# 5 代码,而使用 C# 4 它可能可以编译正常,但实际上会被破坏。)
关于c# - 线程池 - 可能的线程执行顺序问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4469554/