使用lambda表达式和匿名方法的ThreadPool.QueueUserWorkItem

26

向线程池中的新线程传递两个参数有时可能很复杂,但是使用 lambda 表达式和匿名方法似乎可以实现这一点:

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) 的不可变类型的新类。但在这一点上,这似乎只是额外的工作而没有好处。


1
为什么不直接写o => test.DoWork(s1, s2),而不是更冗长的定义呢? - Mehrdad Afshari
@Mehrdad:因为我对lambda表达式真的很新。;) - 谢谢! - Scott Whitlock
@Mehrdad:我在问题中进行了更改。 - Scott Whitlock
5个回答

17

这没有什么问题。编译器基本上自动完成了您描述的替代方案。它创建一个类来保存捕获的变量(test、s1和s2),并将一个委托实例传递给lambda,该实例被转换为匿名类上的方法。换句话说,如果您使用替代方案继续进行,您最终会得到与编译器为您生成的非常相似的东西。


4

对于这个特定的例子,没有任何问题。您传递到其他线程的状态是完全包含的,所涉及的类型都没有任何线程关联问题。


那么关于一般模式呢?如何知道一个类型是否存在线程亲和性问题? - Joel Coehoorn
基本上,您正在询问一个类是否是线程安全的。在我的特定实现中(在大多数情况下),我使用深度不可变对象使它们线程安全。使对象线程安全的其他方法是使用锁等。 - Scott Whitlock
@Joel 如果一个类型存在线程亲和性问题,那么你就完蛋了。你无法在单独的线程上对其进行任何操作。不过,总体模式是正确的(我经常使用它)。 - JaredPar
@Scott,不是的。线程亲和性与线程安全非常不同。线程亲和性意味着一个对象只能从特定的线程使用(可以想象WinForms、WPF)。 - JaredPar
@JaredPar,抱歉,你是正确的。你指的是DoWork函数是否尝试访问表单上的控件,例如。在这种情况下,您必须检查是否需要调用,然后在必要时执行调用。 - Scott Whitlock

2

这种设计模式存在一个潜在问题,就是很容易将它扩展成更加通用但不够安全的形式,例如下面这段代码(仅作示例,不保证可用):

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
    }
}

在我的情况下,我有一个深度不可变的基类,我可以用它来钉住T1和T2...所以你可以使这个在我的特定情况下工作。如果你不了解线程问题,那么使用线程池也是不可能的。 - Scott Whitlock
同意:您的特定情况没有问题。但如果将其实现为通用模式,则存在一些顾虑。 - Joel Coehoorn

2

这是一种不错的做法。使用lambda表达式没有明显的缺点,而且简单干净。


2
您所看到的是闭包。正如 chuckj所说,编译器在编译时生成一个与在闭包外部访问的成员相对应的类。
唯一需要担心的是是否有ref或out参数。虽然字符串是不可变的,但对它们(或任何变量)的引用却不是。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接