首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ThreadPool.QueueUserWorkItem的意外行为

ThreadPool.QueueUserWorkItem的意外行为
EN

Stack Overflow用户
提问于 2011-01-20 15:50:49
回答 1查看 2.7K关注 0票数 6

请检查下面的代码示例:

代码语言:javascript
复制
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();
    }

}

此示例的预期输出应如下所示:

代码语言:javascript
复制
Sample-0 : 0 
Sample-1 : 0 
Sample-2 : 0 
Sample-3 : 0 
Sample-0 : 1 
Sample-1 : 1 
Sample-2 : 1 
Sample-3 : 1
.
. 
.

但是,当您运行此代码时,它将显示如下所示:

代码语言:javascript
复制
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都显示为Sample-3,而执行显然是相互独立的。

不是有不同的对象在使用不同的线程吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2011-01-20 15:54:45

这是旧的修改过的闭包问题。你可能想看看:Threadpools - possible thread execution order problem上有类似的问题,Eric Lippert的博客文章Closing over the loop variable considered harmful上有关于这个问题的理解。

从本质上讲,这里的lambda表达式捕获的是变量s,而不是声明lambda时的变量值。因此,对变量的值所做的后续更改对委托是可见的。RunCount方法将在其上运行的Sample实例将依赖于委托实际执行点的变量s (其值)所引用的实例。

此外,由于委托(编译器实际上重用了相同的委托实例)是异步执行的,因此不能保证每次执行时这些值将是什么。您当前看到的是,foreach循环在主线程上完成,比任何委托调用都要早(不出所料--在线程池上调度任务需要时间)。因此,所有工作项最终都会看到循环变量的“最终”值。但这并不能以任何方式保证;尝试在循环中插入一个持续时间合理的Thread.Sleep,您将看到一个不同的输出。

通常的解决方法是:

  1. 在loop-body.
  2. Assign中引入了另一个变量,该变量将循环的当前值-变量。
  3. 捕获的是'copy‘变量,而不是lambda中的循环变量。

foreach (arrSample中的样本s){ Sample sCopy = s;ThreadPool.QueueUserWorkItem(callback => sCopy.RunCount());}

现在,每个工作项都“拥有”循环变量的特定值。

在这种情况下,另一种选择是通过不捕获任何内容来完全回避问题:

代码语言:javascript
复制
ThreadPool.QueueUserWorkItem(obj => ((Sample)obj).RunCount(), s);
票数 11
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4744630

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档