对于ThreadPool.QueueUserWorkItem意外行为 [英] Unexpected behaviour for ThreadPool.QueueUserWorkItem

查看:174
本文介绍了对于ThreadPool.QueueUserWorkItem意外行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请检查下面的code样品:<​​/ P>

Please check the code sample below:

public class Sample
{
    public int counter { get; set; }
    public string ID;
    public void RunCount()
    {
        for (int i = 0; i < counter; i++)
        {
            Thread.Sleep(1000);

            Console.WriteLine(this.ID + " : " + i.ToString());
        }
    }
}

class Test
{
    static void Main()
    {
        Sample[] arrSample = new Sample[4];

        for (int i = 0; i < arrSample.Length; i++)
        {
            arrSample[i] = new Sample();
            arrSample[i].ID = "Sample-" + i.ToString();
            arrSample[i].counter = 10;
        }

        foreach (Sample s in arrSample)
        {
            ThreadPool.QueueUserWorkItem(callback => s.RunCount());
        }

        Console.ReadKey();
    }

}

此示例的预期输出应该是这样的:

The expected output for this sample should be something like :

Sample-0 : 0 
Sample-1 : 0 
Sample-2 : 0 
Sample-3 : 0 
Sample-0 : 1 
Sample-1 : 1 
Sample-2 : 1 
Sample-3 : 1
.
. 
.

不过,当您运行此code,它会显示这样的事情,而不是:

However, when you run this code, it would show something like this instead:

Sample-3 : 0 
Sample-3 : 0 
Sample-3 : 0 
Sample-3 : 1 
Sample-3 : 1 
Sample-3 : 0 
Sample-3 : 2 
Sample-3 : 2
Sample-3 : 1 
Sample-3 : 1
.
. 
.

我可以理解,这些线程正在执行可能会有所不同的顺序,因此计数心不是在循环的方式增加。不过,我不明白,为什么所有的 ID ,则显示为样品-3 <​​/ code>,而执行力清楚地发生相互独立的。

I can understand that the order in which the threads are executing might differ and hence the count isnt increasing in round robin fashion. However, I fail to understand, why all the IDs are being displayed as Sample-3, while the execution is clearly happening independent of each other.

ARENT不同的对象与不同的线程中使用?

Arent different objects being used with different threads?

推荐答案

这是旧的修改封闭的问题。你可能想看看:<一href="http://stackoverflow.com/questions/4469554/threadpools-possible-thread-execution-order-problem/4469581#4469581">Threadpools - 可能的线程执行顺序的问题的一个类似的问题,并埃里克利珀的博客文章<一个href="http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx"相对=nofollow>关闭了被认为是有害对这一问题的理解循环变量。

This is the old modified closure problem. You might want to look at: Threadpools - possible thread execution order problem for a similar question, and Eric Lippert's blog post Closing over the loop variable considered harmful for an understanding of the issue.

从本质上讲,你已经得到了拉姆达EX pression有捕获的变量取值而非值。因此,给变量的的值所作的后续变化是的可见的委托。 样品上的 RunCount 方法将运行将取决于实例的变量引用实例取值(其值)在点代表实际执行的。

Essentially, the lambda expression you've got there is capturing the variable s rather than the value of the variable at the point the lambda is declared. Consequently, subsequent changes made to the value of the variable are visible to the delegate. The instance of Sample on which the RunCount method will run will depend on the instance referred to by the variable s (its value) at the point the delegate actually executes.

此外,由于代表(多个)(编译器实际重复使用相同的委托实例)被异步执行,也不能保证这些值的将是在每次执行点。什么现在你看到的是,的foreach 循环的主线程中完成的的任何委托,调用(的可以预期的 - 它需要时间安排上的线程池的任务)。所以的所有的工作项最终seing循环变量的最终的价值。但是这并不是以任何方式保证;请尝试将一个合理的持续时间 Thread.sleep代码循环中,你会看到一个不同的输出。

Additionally, since the delegate(s) (the compiler actually reuses the same delegate instance) are being asynchronously executed, it isn't guaranteed what these values will be at the point of each execution. What you are currently seeing is that the foreach loop completes on the main-thread before any of the delegate-invocations (to be expected - it takes time to schedule tasks on the thread-pool). So all the work-items end up seing the 'final' value of the loop-variable. But this isn't guaranteed by any means; try inserting a reasonable-duration Thread.Sleep inside the loop, and you will see a different output.

通常的解决方法是:

  1. 在介绍另一个变量的的循环体。
  2. 分配该变量的循环变量的当前值。
  3. 夺复制变量而不是拉姆达内部的循环变量。

  1. Introduce another variable inside the loop-body.
  2. Assign that variable to the current value of the loop-variable.
  3. Capture the 'copy' variable instead of the loop-variable inside the lambda.

foreach (Sample s in arrSample)
{
    Sample sCopy = s;
    ThreadPool.QueueUserWorkItem(callback => sCopy.RunCount());
}

现在每一个工作项拥有循环变量的一个特定的值。

Now each work-item "owns" a particular value of the loop variable.

在这种情况下,另一种选择是躲闪的问题完全没有捕捉什么:

Another option in this case is to dodge the issue completely by not capturing anything:

ThreadPool.QueueUserWorkItem(obj => ((Sample)obj).RunCount(), s);

这篇关于对于ThreadPool.QueueUserWorkItem意外行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆