Lambda表达式、捕获变量和线程处理

10
我知道.NET Lambda表达式可以捕获外部变量。 不过,我经常看到变量作为参数明确传递给Lambda表达式,并且.NET库似乎也支持这种方式(例如ThreadPool.QueueUserWorkItem)。
我的问题是这些捕获的限制是什么?那么对于实际上在不同线程上执行的lambda(例如ThreadPool.QueueUserWorkItem或Thread),或者充当回调的lambda(即稍后调用的lambda),应该怎么办?
通常情况下,我应该依赖于捕获的变量,何时使用显式参数?例如:
public void DoStuff()
{
     string message = GetMessage();

     ThreadPool.QueueUserWorkItem(s => SendMessage(message)); // use captured variable
     // -- OR --
     ThreadPool.QueueUserWorkItem(s =>
          {
               string msg = (string)s;
               SendMessage(msg);
          }, message); // use explicit parameter
}

谢谢!

更新:修复了第二个ThreadPool.QueueUserWorkItem示例的问题。

3个回答

9
我认为在你的第一个例子中,你的意思是:
QueueUserWorkItem( () => SendMessage(message) );

在您的第二个项目中,参数s来自哪里?我想您的意思是
QueueUserWorkItem( s => {SendMessage((string)s);} , message );

现在,这两者是等效的,但是:
  • 在第一种情况下:参数message从此DoStuff方法的范围中复制,并直接存储在您的lambda表达式本身中作为闭包。 Lambda保留了message的副本。

  • 在第二种情况下:message被发送到Queue,队列将其保存(以及lambda),直到调用lambda。它将在运行lambda时传递给lambda。

我认为第二种情况在程序上更灵活,因为它理论上可以允许您在调用lambda之前稍后更改message参数的值。然而,第一种方法更容易阅读,并且更具有抵御副作用的能力。但实际上,两者都可以。

你说得对,我在示例的第二部分中漏掉了参数。所以我理解,在实践中使用哪个并不重要,但是你建议我使用捕获变量,因为它更简单(和可读性更好)。非常感谢您详细的回答! - ShdNx

5

对于您的示例(指不可变字符串对象的引用),这完全没有影响。

一般来说,复制引用并不会有太大的影响。但在处理值类型时确实很重要:

 double value = 0.1;

 ThreadPool.QueueUserWorkItem( () => value = Process(value)); // use captured variable

 // -- OR --

 ThreadPool.QueueUserWorkItem( () =>
      {
           double val = value;      // use explicit parameter
           val = Process(val);      // value is not changed
      }); 

 // -- OR --

 ThreadPool.QueueUserWorkItem( (v) =>
      {
           double val = (double)v;  // explicit var for casting
           val = Process(val);      // value is not changed
      }, value); 

第一个版本不是线程安全的,第二个和第三个版本可能是线程安全的。
最后一个版本使用了“对象状态”参数,这是Fx2(Linq之前)的功能。

我必须承认,我觉得你的示例代码很令人困惑。在第一部分中,你使用了参数,但是你没有将它传递给lambda函数。但是根据我的理解,这并不是使用捕获变量(captured variable),因为那意味着直接使用value变量的值。你的第二个示例指出了不可变类型的行为,这没问题,但它并没有帮助我理解其中的区别。你能否更新你的答案以澄清这一点? - ShdNx
谢谢,现在很清楚了。唯一的问题是您应该更新代码中的注释。 因为提到了线程安全性(尽管仅适用于不可变类型),所以加1分,但我选择接受Sanjay Manohar的答案,因为它更快、更清晰、更详细。也谢谢您的帮助! - ShdNx

2
  1. 如果您希望匿名函数引用相同的状态,请从常规上下文中捕获标识符。
  2. 如果您想要无状态的Lambda表达式(在并发应用中我建议使用),请使用本地副本。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接