简体   繁体   中英

Threadpools - possible thread execution order problem

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?

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.)

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM