【问题标题】:Starting a thread with / without delegate()使用/不使用 delegate() 启动线程
【发布时间】:2010-11-09 13:38:26
【问题描述】:

有什么区别:

new Thread(new ThreadStart(SomeFunc))

和:

new Thread( delegate() { SomeFunc();} )

这段代码在我的电脑上给出了奇怪的输出:

public class A
{
    int Num;

    public A(int num)
    {
        Num = num;
    }

    public void DoObj(object obj)
    {
        Console.Write(Num);
    }

    public void Do()
    {
        Console.Write(Num);
    }
}

/////// in void main()

for (int i = 0; i < 10; i++)
{
    (new Thread(new ThreadStart((new A(i)).Do))).Start(); // Line 1
    (new Thread(new ThreadStart(delegate() { (new A(i)).Do(); }))).Start(); // Line 2
    (new Thread(delegate() { (new A(i)).Do(); })).Start(); // Line 3
}

如果只执行第 1 行,则输出类似于:

0 2 3 1 5 6 4 7 8 9

没关系,但是如果执行第 2 行或第 3 行,输出是:

3 3 3 5 5 7 7 9 9 10

有一些多个数字和一个 10,这很奇怪,循环永远不会以数字 10 运行。这些背后的技巧是什么?

谢谢。

【问题讨论】:

标签: c# multithreading delegates


【解决方案1】:

使用委托,您正在捕获i

不同之处在于,使用new ThreadStart((new A(i)).Do)),您将在for 循环中以i 作为参数创建A 的新实例。这意味着此时,i 的值被获取并发送到构造函数。因此,您发送的委托不是A 的创建,而是您实际上将A 实例的Do 方法的委托发送给构造函数。

但是,对于delegate() { (new A(i)).Do(); })(两者),您将i 的引用发送到线程。

线程需要一些时间才能启动,与此同时,for 循环继续进行。当 i 用于委托(即线程已启动)时,for 循环已移动到 3,这就是您所看到的。第二个和第三个线程也是如此。这三个线程已启动,但要等待启动线程完成一些工作。然后创建的线程启动(线程 1、2 和 3)并开始工作。 Windows 回到带有for 循环的线程并继续启动线程 4 和 5。

一些阅读材料:

【讨论】:

  • 有关详细信息,另请参阅 Eric Lippert 的 this article
  • 感谢您的链接;添加到答案中。
  • +1 很好的解释,昨天我自己在 for 循环中使用带有匿名委托的新 Task.Factory.StartNew 调用遇到了这个问题。它导致了一些细微的错误。大多数情况下一切正常,但如果系统繁忙,则多个任务使用相同的数据。
  • 那么我必须自己复制索引变量。例如国际温度=我; delegate() { (new A(temp)).Do();} 对吗?
  • @bahadir - 没错。在 for 循环中,在 new Thread 之前,按照您的描述创建变量的副本,您的问题就消失了。
【解决方案2】:

为了回答您的第一点,delegate() { SomeFunc();} 创建了一个调用SomeFunc() 的函数,而不使用delegate() 只是将SomeFunc 函数直接用作ThreadStart 方法。

在第二个问题中,您遇到了 C# 匿名函数的实现细节。对i 的所有三个引用都引用了相同 i,它增加了三倍。这三个函数之间存在竞争条件,这意味着 i 可以在启动的线程运行之前递增几次。

【讨论】:

  • 我不会将其称为实现细节。这是非常基本的。实现细节是用户不关心的。
【解决方案3】:

'什么时候调用对象 A 的构造函数?'有助于回答问题。

new ThreadStart((new A(i)).Do))

当这行代码被执行时 - 构造函数被调用并且对新创建的对象 A 上的 .Do 函数的引用由 ThreadStart 委托保存。

在第 2 行和第 3 行中,您正在使用匿名委托(在 C# 2.0 中引入)。

delegate() { (new A(i)).Do(); })

匿名委托的内容在委托被调用之前不会被执行;在这种情况下,由线程分配时间片来执行此操作。

变量 i 仅在 for 循环开始时声明一次,并且委托内容有对它的引用(委托会这样做) - 当代码执行时,它必须在执行时获取 i 的值执行。

这解释了值 10。当循环完成执行时,i 的值为 10。如果其中一个线程在循环结束后执行,它将输出 10。

为避免多个数字问题,您可以创建循环变量的本地副本。委托将保留对其自己版本 icopy 的引用;

for (int i = 0; i < 10; i++)
{
     int icopy = i;
     (new Thread(new ThreadStart(delegate() { (new A(icopy)).Do(); }))).Start();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-16
    • 1970-01-01
    • 2015-04-15
    • 2023-04-07
    • 1970-01-01
    • 1970-01-01
    • 2020-02-19
    • 1970-01-01
    相关资源
    最近更新 更多