【问题标题】:Lambda expressions, captured variables and threadingLambda 表达式、捕获的变量和线程
【发布时间】:2010-10-10 11:03:23
【问题描述】:

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

我的问题是这些捕获的限制是什么?实际在不同线程上执行的 lambdas 与创建它们的线程(例如 ThreadPool.QueueUserWorkItem 或 Thread)或充当回调(即稍后调用)的 lambdas 怎么样?

一般来说,什么时候应该依赖捕获的变量,什么时候应该使用显式参数?例如:

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 示例。

【问题讨论】:

    标签: c# .net multithreading lambda


    【解决方案1】:

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

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

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

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

    现在,这两者的工作方式相同,但是

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

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

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

    【讨论】:

    • 你说得对,我错过了例子第二部分的参数。据我了解,在实践中,我使用哪一个并不重要,但由于简单(和可读性),您建议我使用捕获的变量。非常感谢您的详细解答!
    【解决方案2】:

    对于您的示例(对不可变字符串对象的引用),它完全没有区别。

    一般来说,复制一份参考资料不会有太大的不同。使用值类型时,这很重要:

     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(LINQ 之前的)功能。

    【讨论】:

    • 我不得不承认我发现您的示例代码令人困惑。在第一部分中,您正在使用参数,但您没有将它传递给执行 lambda。但是那不是使用捕获的变量(根据我的理解),因为这意味着直接使用 value 变量。您的第二个示例指出了不可变类型的行为方式,这很好,但这并不能帮助我理解其中的区别。您能否更新您的答案以澄清这一点?
    • 谢谢,现在很清楚了。唯一的问题是您还应该更新代码中的 cmets。 +1 提到线程安全(虽然它只适用于不可变类型),但我接受了 Sanjay Manohar 的回答,因为它更快、更清晰、更详细。也感谢您的帮助!
    【解决方案3】:
    1. 如果您希望匿名函子引用相同的状态,请从公共上下文中捕获标识符。
    2. 如果您想要无状态 lambda(我建议在并发应用程序中使用),请使用本地副本。

    【讨论】:

      猜你喜欢
      • 2015-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多