如何在C#中向线程传递参数

8

我搜索过这个问题,发现很多答案,不难找到解决办法。 但是,我有一个奇怪的经历,不知道原因,所以请人们给我一些建议。 下面是我的代码:

    void SetThread()
    {
        for (int i = 0; i < _intArrayLength; i++)
        {
            Console.Write(string.Format("SetThread->i: {0}\r\n", i));
            _th[i] = new Thread(new ThreadStart(() => RunThread(i)));
            _th[i].Start();
        }
    }

    void RunThread(int num)
    {
        Console.Write(string.Format("RunThread->num: {0}\r\n", num));
    }

是的,它们是普通的线程代码。 我希望所有线程数组都应该调用RunThread方法10次。 应该像这样:

SetThread->i: 0
SetThread->i: 1
SetThread->i: 2
SetThread->i: 3
SetThread->i: 4
SetThread->i: 5
SetThread->i: 6
SetThread->i: 7
SetThread->i: 8
SetThread->i: 9
RunThread->num: 0
RunThread->num: 1
RunThread->num: 2
RunThread->num: 3
RunThread->num: 4
RunThread->num: 5
RunThread->num: 6
RunThread->num: 7
RunThread->num: 8
RunThread->num: 9

这就是我期望的结果。顺序不重要。 但我得到的结果如下。

SetThread->i: 0
SetThread->i: 1
SetThread->i: 2
The thread '<No Name>' (0x18e4) has exited with code 0 (0x0).
The thread '<No Name>' (0x11ac) has exited with code 0 (0x0).
The thread '<No Name>' (0x1190) has exited with code 0 (0x0).
The thread '<No Name>' (0x1708) has exited with code 0 (0x0).
The thread '<No Name>' (0xc94) has exited with code 0 (0x0).
The thread '<No Name>' (0xdac) has exited with code 0 (0x0).
The thread '<No Name>' (0x12d8) has exited with code 0 (0x0).
The thread '<No Name>' (0x1574) has exited with code 0 (0x0).
The thread '<No Name>' (0x1138) has exited with code 0 (0x0).
The thread '<No Name>' (0xef0) has exited with code 0 (0x0).
SetThread->i: 3
RunThread->num: 3
RunThread->num: 3
RunThread->num: 3
SetThread->i: 4
RunThread->num: 4
SetThread->i: 5
SetThread->i: 6
RunThread->num: 6
RunThread->num: 6
SetThread->i: 7
RunThread->num: 7
SetThread->i: 8
RunThread->num: 8
SetThread->i: 9
RunThread->num: 9
RunThread->num: 10

我期望RunThread函数可以将参数(num)从0到9传递。但我无法理解错误信息是什么意思。"线程''~~等等。有人能给我一些提示吗?

1
错误消息没什么意义,我觉得它只是Visual Studio的调试输出。 - millimoose
3
所有线程都通过闭包引用循环计数器 i,这个闭包是由传递给 ThreadStart 的 lambda 表达式创建的。它们不会在创建 Thread 对象时获得当前值的副本,而是在最终调用 RunThread 时获取 i 的值,这是在启动线程并完成初始化等操作后发生的 - 此时循环将已经进行了一段时间。正如您从输出中看到的那样,这正是发生的情况,RunThread 打印出在 SetThread 中看到的最后一个值。 - millimoose
1
无论如何,我认为你可以通过在for循环内部执行var ii = i;,并在ThreadStart的参数中使用ii来防止这种情况发生。这将使lambda表达式关闭不同的局部变量“实例”。(区别在于循环计数器的作用域在循环体外部,是一个局部变量内部。)除非我把JavaScript和C#搞混了。 - millimoose
1
一个常见的问题,参见:http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx - Blorgbeard
有趣的是,来自MSDN:我们正在进行重大变革。在C# 5中,foreach循环的循环变量将逻辑上位于循环内部,因此闭包将每次都会关闭变量的新副本。 "for"循环不会改变。 - Ryan
2个回答

6
您正在“循环变量上创建闭包”,一个简单的解决方法是创建一个本地副本,这样您的线程将使用期望的值。请参考此文章
void SetThread()
    {
        for (int i = 0; i < _intArrayLength; i++)
        {
           int currentValue = i;
            Console.Write(string.Format("SetThread->i: {0}\r\n", i));
            _th[i] = new Thread(() => RunThread(currentValue));
            _th[i].Start();
        }
    }

感谢您的回答,抱歉我回复晚了。 - Joshua Son

3

您可能需要像这样更改您的代码,使用ParameterizedThreadStart委托:

    for (int i = 0; i < _intArrayLength; i++)
    {
        Console.Write(string.Format("SetThread->i: {0}\r\n", i));
        _th[i] = new Thread((a) => RunThread((int)a));
        _th[i].Start(i);
    }

否则,从您的线程入口点委托() => RunThread(i),您将从父主线程的上下文中访问变量i,这可能会在新线程甚至开始之前发生更改。

自从Lambda出现以来,参数化线程已经有点过时了。 - usr
@usr,我认为这两件事情并没有关联。Lambda表达式仍然是我上面代码中的一种快捷方式,但我可以重写它而不使用lambda,也不对线程启动进行参数化,就像@BrokenGlass所做的那样:new Thread(delegate() { RunThread(currentValue); }) - noseratio - open to work
我的措辞不够准确。我指的是C#支持的任何形式的闭包。 - usr
@usr,我同意。这完全取决于匿名委托如何处理在同一作用域中定义的变量。正如这篇博客系列所称,有“易处理”和“难处理”两种类型。将委托参数化的一个小优点可能是,只要它不使用其父作用域的局部变量,编译器就不会在幕后创建一个匿名类(以及其运行时实例),否则需要支持语法上的好处。我仍然在像上面那样简单的情况下使用它 :) - noseratio - open to work

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