Лямбда-выражения, захваченные переменные и потоки - PullRequest
7 голосов
/ 10 октября 2010

Я знаю, что .NET лямбда-выражения могут захватывать внешние переменные. Однако я часто видел, как переменные явно передаются лямбда-выражению в качестве параметра, и библиотека .NET также поддерживает это (например, ThreadPool.QueueUserWorkItem).

Мой вопрос: каковы ограничения этих захватов? Как насчет лямбд, которые на самом деле выполняются в потоке, отличном от того, в котором они были созданы (например, ThreadPool.QueueUserWorkItem, или Thread), или в виде лямб, которые действуют как обратные вызовы (т.е. вызываются позже)

Как правило, когда мне следует полагаться на захваченные переменные и когда использовать явные параметры? Например:

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 голосов
/ 10 октября 2010

Я думаю, что в вашем первом примере. Вы имеете в виду

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

Во втором пункте, откуда берется параметр s? Я думаю, что вы имеете в виду

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

Теперь эти два работают одинаково, но

  • В первом случае: параметр message скопировано из области видимости этот DoStuff метод и хранится прямо в вашем лямбда-выражении сам, как закрытие. Лямбда имеет хранит копию message.

  • Во втором случае: message отправлено до Queue, и очередь сохраняет держать его (вместе с лямбда), пока лямбда не называется. это прошло во время запуска лямбда, к лямбде.

Я бы сказал, что второй случай более гибок в программном плане, поскольку теоретически может позволить позже изменить значение параметра message до вызова лямбды Однако первый метод легче читать и он более невосприимчив к побочным эффектам. Но на практике оба работают.

5 голосов
/ 10 октября 2010

Для вашего примера (ссылка на неизменяемый строковый объект) это не имеет абсолютно никакого значения.

И вообще, создание копии ссылки не будет иметь большого значения.Имеет значение при работе с типами значений:

 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).

1 голос
/ 10 октября 2010
  1. Если вы хотите, чтобы анонимные функторы ссылались на одно и то же состояние, захватите идентификатор из общего контекста.
  2. Если вам нужны лямбды без сохранения состояния (как я бы рекомендовал в параллельном приложении), используйте локальные копии.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...