ThreadPool.QueueUserWorkItem с лямбда-выражением и анонимным методом

Передача двух параметров в новый поток в пуле потоков иногда может быть сложной, но похоже, что с лямбда-выражениями и анонимными методами я могу сделать это:

public class TestClass
{
    public void DoWork(string s1, string s2)
    {
        Console.WriteLine(s1);
        Console.WriteLine(s2);
    }
}

try
{
    TestClass test = new TestClass();
    string s1 = "Hello";
    string s2 = "World";
    ThreadPool.QueueUserWorkItem(
        o => test.DoWork(s1, s2)
        );
}
catch (Exception ex)
{
    //exception logic
}

Я, конечно, упростил этот пример, но ключевыми являются следующие моменты:

  • Передаваемые строковые объекты являются неизменяемыми и, следовательно, потокобезопасными.
  • Переменные s1 и s2 объявлены в рамках блока try, из которого я выхожу сразу после постановки работы в очередь в пул потоков, поэтому переменные s1 и s2 после этого никогда не изменяются.

Что-то не так с этим?

Альтернативой является создание нового класса, реализующего неизменяемый тип с тремя элементами: test, s1 и s2. Это просто кажется дополнительной работой без какой-либо пользы на данный момент.


person Scott Whitlock    schedule 10.04.2009    source источник
comment
Почему бы вам просто не написать o => test.DoWork(s1, s2) вместо более подробного определения?   -  person mmx    schedule 10.04.2009
comment
@Mehrdad: Потому что я действительно новичок в лямбда-выражениях. ;) - Благодарность!   -  person Scott Whitlock    schedule 10.04.2009
comment
@Mehrdad: я изменил это в вопросе.   -  person Scott Whitlock    schedule 10.04.2009


Ответы (5)


В этом нет ничего плохого. Компилятор, по сути, автоматически делает то, что вы описали как альтернативу. Он создает класс для хранения захваченных переменных (test, s1 и s2) и передает экземпляр делегата лямбда-выражению, которое превращается в метод анонимного класса. Другими словами, если вы продолжите свой вариант, вы получите что-то очень похожее на то, что компилятор только что сгенерировал для вас.

person chuckj    schedule 10.04.2009

Для этого конкретного примера здесь нет ничего плохого. Состояние, которое вы передали в другой поток, полностью содержится, и ни один из задействованных типов не имеет проблем с привязкой к потоку.

person JaredPar    schedule 10.04.2009
comment
А как насчет общей схемы? Как узнать, есть ли у типа проблемы с привязкой к потоку? - person Joel Coehoorn; 10.04.2009
comment
В основном вы спрашиваете, является ли класс потокобезопасным или нет. В моей конкретной реализации (в большинстве случаев) я использую глубоко неизменяемые объекты, чтобы сделать их потокобезопасными. Другие способы сделать объекты потокобезопасными — использовать блокировку и т. д. - person Scott Whitlock; 10.04.2009
comment
@Joel, если у типа есть проблемы с привязкой к потоку, вам конец. Вы ничего не можете сделать с ним в отдельном потоке. Хотя общий шаблон звучит хорошо (я часто его использую) - person JaredPar; 10.04.2009
comment
@ Скотт, нет. ThreadAffinity сильно отличается от потокобезопасности. Сходство с потоком означает, что объект может использоваться только из определенного потока (например, WinForms, WPF). - person JaredPar; 10.04.2009
comment
@JaredPar, извините, вы правы. Например, вы имеете в виду, пытается ли функция DoWork получить доступ к элементу управления в форме. В этом случае вам нужно будет проверить, требуется ли Invoke, а затем при необходимости выполнить Invoke. - person Scott Whitlock; 10.04.2009

Это хороший способ сделать это. Я не вижу никаких недостатков использования лямбд. Это просто и понятно.

person mmx    schedule 10.04.2009

То, на что вы смотрите, называется закрытием. Как указывает chuckj, компилятор генерирует класс во время компиляции, который соответствует членам, доступ к которым осуществляется вне замыкания.

Единственное, о чем вам нужно беспокоиться, это если у вас есть параметры ref или out. Хотя строки неизменяемы, ссылки на них (или любую переменную) НЕ являются.

person casperOne    schedule 10.04.2009

Одна потенциальная проблема с шаблоном заключается в том, что очень заманчиво расширить его до чего-то более общего, но менее безопасного, как это (скретч-код — не ждите, что он сработает):

public static void QueueTwoParameterWorkItem<T1, T2>(T1 value1, T2 value2, workDelegate<T1,T2> work)
{
    try
    {
        T1 param1 = value1;
        T2 param2 = value2;
        ThreadPool.QueueUserWorkItem(
            (o) =>
            {
                work(param1, param2);
            });
    }
    catch (Exception ex)
    {
        //exception logic
    }
}
person Joel Coehoorn    schedule 10.04.2009
comment
В моем случае у меня есть глубоко неизменяемый базовый класс, который я мог бы использовать для привязки T1 и T2... так что вы могли бы заставить это работать в моем конкретном случае. Если вы не понимаете проблем с потоками, то об использовании пула потоков в любом случае не может быть и речи. - person Scott Whitlock; 10.04.2009
comment
Согласен: ваш конкретный случай в порядке. Когда вы реализуете его как общий шаблон, возникают проблемы. - person Joel Coehoorn; 10.04.2009