Lambda表达式,捕获变量和线程

时间:2010-10-10 11:03:24

标签: c# .net multithreading lambda

我知道.NET lambda表达式可以捕获外部变量。 但是,我已经很多次看到变量作为参数显式传递给lambda表达式,而.NET库似乎也支持它(例如ThreadPool.QueueUserWorkItem)。

我的问题是这些捕获的限制是什么?如果lambda实际上是在与创建它们的线程不同的线程上执行的(例如ThreadPool.QueueUserWorkItem或Thread),或者作为回调的lambas(即稍后调用)?

通常,何时应该依赖捕获的变量,何时使用显式参数?例如:

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 个答案:

答案 0 :(得分:9)

我想在你的第一个例子中,你的意思是

QueueUserWorkItem( () => SendMessage(message) );

在你的第二个项目中,参数s来自哪里?我想你的意思是

QueueUserWorkItem( s => {SendMessage((string)s);} , message );

现在,这两个都是等效的,但是

  • 在第一种情况下:参数 message是从范围复制的 这个DoStuff方法并存储起来 直接在你的lambda表达式中 本身,作为一个封闭。 lambda有 保留message的副本。

  • 在第二种情况下:message已发送 到Queue,队列保持不变 抓住它(连同lambda), 直到lambda被调用。它是 在运行时通过 lambda,到lambda。

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

答案 1 :(得分: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); 

第一个版本不是线程安全的,第二个和第三个版本可能是。

最后一个版本使用object state参数,该参数是Fx2(pre-LINQ)功能。

答案 2 :(得分:1)

  1. 如果您希望匿名仿函数引用相同的状态,请从公共上下文中捕获标识符。
  2. 如果你想要无状态lambda(正如我在并发应用程序中所推荐的那样),请使用本地副本。