C# Lambda表达式、捕获的变量和线程

C# Lambda表达式、捕获的变量和线程,c#,.net,multithreading,lambda,C#,.net,Multithreading,Lambda,我知道.NET lambda表达式可以捕获外部变量。 但是,我多次看到变量作为参数显式传递给lambda表达式,而.NET库似乎也支持这一点(例如ThreadPool.QueueUserWorkItem) 我的问题是,这些捕获的局限性是什么?在不同的线程上实际执行的lambda(例如ThreadPool.QueueUserWorkItem或thread)或作为回调(即稍后调用)的lamba如何 通常,我应该何时依赖捕获的变量,何时使用显式参数?例如: public void DoStuff()

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

我的问题是,这些捕获的局限性是什么?在不同的线程上实际执行的lambda(例如ThreadPool.QueueUserWorkItem或thread)或作为回调(即稍后调用)的lamba如何

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

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

我认为在您的第一个示例中,您的意思是

QueueUserWorkItem( () => SendMessage(message) );
在第二项中,参数
s
来自何处?我想你是说

QueueUserWorkItem( s => {SendMessage((string)s);} , message );
现在,这两个都是等效的,但是

  • 在第一种情况下:参数
    消息
    是从 此
    DoStuff
    方法并存储 直接在lambda表达式中 它本身,作为一个终结。兰姆达号 保留
    消息的副本

  • 在第二种情况下:
    消息
    被发送 到
    队列
    ,队列保持 抓住它(和兰姆达一起), 直到lambda被调用。它是 在运行 兰姆达,兰姆达

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

对于您的示例(对不可变字符串对象的引用),这两种方法完全没有区别

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

 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(正如我在并发应用程序中建议的那样),请使用本地副本

  • 你说得对,我错过了示例第二部分中的参数。因此,据我所知,在实践中,我使用哪一个并不重要,但您建议我使用捕获的变量,因为它简单(和可读性)。非常感谢您的详细回答!我必须承认我发现你的示例代码令人困惑。在第一部分中,您使用了参数,但没有将其传递给lambda。但这并不是使用捕获的变量(根据我的理解),因为这意味着直接使用value变量。您的第二个示例指出了不可变类型的行为,这是正确的,但它不能帮助我理解其中的区别。你能更新你的答案来澄清这一点吗?谢谢,现在已经清楚了。唯一的问题是,您还应该更新代码中的注释+1提到threadsafety(虽然它只适用于不可变类型),但我接受了Sanjay Manohar的回答,因为它更快、更清晰、更详细。也谢谢你的帮助!