【问题标题】:QueueUserWorkItem with delegate does not work, but WaitCallBack does work带有委托的 QueueUserWorkItem 不起作用,但 WaitCallBack 确实起作用
【发布时间】:2012-10-10 06:43:02
【问题描述】:

在下面的问题中,我发现了一个以类型安全的方式调用 QueueUserWorkItem 的巧妙技巧,您可以传递一个委托而不是 WaitCallBack 和一个对象。然而,它并不像人们期望的那样工作。

What's the difference between QueueUserWorkItem() and BeginInvoke(), for performing an asynchronous activity with no return types needed

这里有一些演示该问题的示例代码和输出。

for (int i = 0; i < 10; ++i)
{
    // doesn't work - somehow DoWork is invoked with i=10 each time!!!
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", i); });

    // not type safe, but it works
    ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), Tuple.Create("    WCB", i));
}

void DoWork(string s, int i)
{
    Console.WriteLine("{0} - i:{1}", s, i);
}

void DoWork(object state)
{
    var t = (Tuple<string, int>)state;
    DoWork(t.Item1, t.Item2);
}

这是输出:

closure - i:10
    WCB - i:0
closure - i:10
    WCB - i:2
    WCB - i:3
closure - i:10
    WCB - i:4
closure - i:10
    WCB - i:5
closure - i:10
    WCB - i:6
closure - i:10
    WCB - i:7
closure - i:10
    WCB - i:8
closure - i:10
    WCB - i:9
    WCB - i:1
closure - i:10

请注意,当使用闭包调用 QueueUserWorkitem 时,i=10 永远调用,但使用 WaitCallBack 时,您会得到正确的值,0-9。

所以我的问题是:

  1. 为什么在使用闭包/委托方式时没有传递正确的 i 值?
  2. 我到底怎么能长到 10 岁?在循环中,它的值只有 0-9 对吗?

【问题讨论】:

  • 循环变量捕获,再次。搜索该术语的堆栈溢出。
  • @usr 或者,您可以找到其中一个重复项并投票关闭。这将是更有效的做法。

标签: c# multithreading delegates


【解决方案1】:

这两个问题的答案都与创建匿名方法时的闭包范围有关。

当你这样做时:

// Closure for anonymous function call begins here.
for (int i = 0; i < 10; ++i)
{
    // i is captured
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", i); });
}

您正在整个循环中捕获i。这意味着您非常快速地将十个线程排队,当它们开始时,闭包已将 i 捕获为 10。

为了解决这个问题,您可以通过在循环中引入一个变量来缩小闭包的范围,如下所示:

for (int i = 0; i < 10; ++i)
{
    // Closure extends to here.
    var copy = i;

    // **copy** is captured
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", copy); });
}

这里,闭包并没有超出循环,而只是延伸到里面的值。

也就是说,对 QueueUserWorkItem 的第二次调用会产生所需的结果,因为您在委托排队时创建了 Tuple&lt;T1, T2&gt;,此时值是固定的。

请注意in C# 5.0, the behavior for foreach was changed because it happens so often (where the closure closes over the loop) and causes a number of people a lot of headaches(但不是您使用的for)。

如果您想利用这一点,您可以在Enumerable class 上拨打Range method 以使用foreach

foreach (int i in Enumerable.Range(0, 10))
{
    // Closure for anonymous function call begins here.
    ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", i); });
}

【讨论】:

    【解决方案2】:

    这是因为变量是如何被捕获的:委托将在实际执行时获取i 的值,而不是在声明时,所以到那时它们都是 10。尝试复制到局部变量:

    for (int i = 0; i < 10; ++i)
    {
        int j = i;        
        ThreadPool.QueueUserWorkItem(delegate { DoWork("closure", j); });
    

    【讨论】:

    • 我明白了。谢谢!如果我在不知道的情况下看到该代码,我会想“wtf - 为什么要复制???”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多