线程池 - 可能的线程执行顺序问题

发布于 2024-10-08 10:14:58 字数 1730 浏览 0 评论 0原文

我一直在学习如何使用线程池,但我不确定池中的每个线程是否都正确执行,并且我怀疑有些线程被执行多次。我已将代码削减到最低限度,并一直在使用 Debug.WriteLine 来尝试弄清楚发生了什么,但这会产生一些奇怪的结果。

我的代码如下(基于 (不支持 STA 线程上的多个句柄的 WaitAll):

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 目前实际上并没有执行任何操作,

如果我手动单步执行代码,我会得到:

Thread 1 started。 线程 2 已启动 ....一路到.... 线程 10 已启动

但是,如果我手动运行它,输出窗口将填充“线程 10 已启动”

我的问题是:我使用线程池的代码是否有问题,或者 Debug.WriteLine 的结果是否被多个线程混淆了?

I've been learning how to use the threadpools but I'm not sure that each of the threads in the pool are being executed properly and I suspect some are being executed more than once. I've cut down the code to the bare minimum and having been using Debug.WriteLine to try and work out what is going on but this produces some odd results.

My code is as follows (based on code from (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());
        }
    }

The classImport.ThreadPoolCallBack doesn't actually do anything at the minute.

If I step through the code manually I get:

Thread 1 started
Thread 2 started
.... all the way to ....
Thread 10 started

However, if I run it manually the Output window is filled with "Thread 10 started"

My question is: is there something wrong with my code for use of the threadpool or is the Debug.WriteLine's results being confused by the multiple threads?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

请远离我 2024-10-15 10:14:58

问题是您在 lambda 表达式中使用循环变量 (j)。

为什么这是一个问题的详细信息非常冗长 - 请参阅 Eric Lippert 的博客文章了解详细信息(另请阅读 第 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 可能可以编译正常,但实际上会被破坏。)

The problem is that you're using the loop variable (j) within a lambda expression.

The details of why this is a problem are quite longwinded - see Eric Lippert's blog post for details (also read part 2).

Fortunately the fix is simple: just create a new local variable inside the loop and use that within the lambda expression:

for (int j = 0; j < doneEvents.Length; j++)
{
    int localCopyOfJ = j;

    ... use localCopyOfJ within the lambda ...
}

For the rest of the loop body it's fine to use just j - it's only when it's captured by a lambda expression or anonymous method that it becomes a problem.

This is a common issue which trips up a lot of people - the C# team have considered changes to the behaviour for the foreach loop (where it really looks like you're already declaring a separate variable on each iteration), but it would cause interesting compatibility issues. (You could write C# 5 code which works fine, and with C# 4 it might compile fine but really be broken, for example.)

°如果伤别离去 2024-10-15 10:14:58

本质上,您获得的局部变量 j 被 lambda 表达式捕获,从而产生旧的 修改关闭问题。您必须阅读该文章才能对这个问题有一个广泛的了解,但我可以在这种情况下谈论一些具体

它可能看起来好像每个线程池任务都看到它自己的j“版本”,但事实并非如此。换句话说,任务创建后后续j的突变对该任务可见。

当您缓慢地单步执行代码时,线程池会在变量有机会更改之前执行每个任务,这就是您获得预期结果的原因(变量的一个值实际上是“关联”的)与一个任务)。在生产中,情况并非如此。看起来,对于您的特定测试运行,循环在任何任务有机会运行之前就完成了。这就是为什么所有任务碰巧看到j相同“最后”值(考虑到在线程池上安排作业所需的时间,我会想象一下这个输出是典型的。)但这并不能以任何方式保证;您可以看到几乎任何输出,具体取决于运行此代码的环境的特定计时特征。

幸运的是,修复方法很简单:

for (int j = 0; j < doneEvents.Length; j++)
{
   int jCopy = j;
   // work with jCopy instead of j

现在,每个任务都将“拥有”循环变量的特定值。

Essentially the local variable j you've got there is captured by the lambda expression, resulting in the old modified closure problem. You'll have to read that post to get a broad understanding of the issue, but I can speak about some specifics in this context.

It might appear as though each thread-pool task is seeing it's own "version" of j, but it isn't. In other words, subsequent mutations to j after a task has been created is visible to the task.

When you step through your code slowly, the thread-pool executes each task before the variable has an opportunity to change, which is why you get the expected result (one value for the variable is effectively "associated" with one task). In production, this isn't the case. It appears that for your specific test run, the loop completed before any of the tasks had an opportunity to run. This is why all of the tasks happened to see the same "last" value for j (Given the time it takes to schedule a job on the thread-pool, I would imagine this output to be typical.) But this isn't guaranteed by any means; you could see pretty much any output, depending on the particular timing characteristics of the environment you're running this code on.

Fortunately, the fix is simple:

for (int j = 0; j < doneEvents.Length; j++)
{
   int jCopy = j;
   // work with jCopy instead of j

Now, each task will "own" a particular value of the loop-variable.

单身情人 2024-10-15 10:14:58

问题是 j 是一个捕获变量,因此每个 lambda 表达式都使用相同的捕获引用。

the problem is that the j is a captured variable and is therefore the same capture reference is being used for each lambda expression.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文