【问题标题】:Starting a new thread in a foreach loop在 foreach 循环中启动一个新线程
【发布时间】:2012-03-14 04:23:53
【问题描述】:

我有一个对象列表,我想遍历该列表并启动一个新线程,传入当前对象。

我已经写了一个我认为应该这样做的示例,但它不起作用。具体来说,似乎线程在每次迭代中都被覆盖了。但这对我来说真的没有意义,因为我每次都在创建一个新的 Thread 对象。

这是我写的测试代码

class Program
{
    static void Main(string[] args)
    {
        TestClass t = new TestClass();
        t.ThreadingMethod();
    }
}

class TestClass
{
    public void ThreadingMethod()
    {
        var myList = new List<MyClass> { new MyClass("test1"), new MyClass("test2") };

        foreach(MyClass myObj in myList)
        {
            Thread myThread = new Thread(() => this.MyMethod(myObj));
            myThread.Start();
        }
    }

    public void MyMethod(MyClass myObj) { Console.WriteLine(myObj.prop1); }
}

class MyClass
{
    public string prop1 { get; set; }

    public MyClass(string input) { this.prop1 = input; }
}

我机器上的输出是

test2
test2

但我预计会是这样

test1
test2

我尝试将线程线更改为

ThreadPool.QueueUserWorkItem(x => this.MyMethod(myObj));

但是没有线程启动。

我想我只是对线程应该如何工作有一个误解。有人可以指出我正确的方向并告诉我我做错了什么吗?

【问题讨论】:

标签: c# .net multithreading foreach


【解决方案1】:

这是因为您在错误的范围内关闭了一个变量。这里的解决方案是在你的 foreach 循环中使用一个临时的:

    foreach(MyClass myObj in myList)
    {
        MyClass tmp = myObj; // Make temporary
        Thread myThread = new Thread(() => this.MyMethod(tmp));
        myThread.Start();
    }

有关详细信息,我建议阅读 Eric Lippert 关于这个确切主题的帖子:Closing over the loop variable considered harmful

【讨论】:

  • 这反应很快。哇。
  • 呸,我在踢自己,因为我实际上在上个月阅读了this question,这就是答案。我什至看了一下 Eric 的博客文章。感谢您指出我自己应该记住的事情。
  • @root45:别用力踢自己。这会咬到每个人,包括我自己。这些年来,我多次编写过同样的错误。
  • @EricLippert:我敢打赌这是关于 SO 的最常见问题(当然以各种不同的方式伪装)。当我说我在今天单独发布的另外两个问题中看到了这个问题时,我并不是在开玩笑。在 C# 的下一版本中将此作为编译器警告(或其他解决方案)的任何更新?我知道你以前谈过。
  • @BrianGideon:阅读我对这个问题的回答:stackoverflow.com/questions/8898925/…
【解决方案2】:

问题在于您使用的是闭包内对象的最新值。因此,线程的每次调用都在查看相同的值。要解决这个问题,请将值复制到局部变量中:

foreach(MyClass myObj in myList)
{
    MyClass localCopy = myObj;
    Thread myThread = new Thread(() => this.MyMethod(localCopy));
    myThread.Start();
}

【讨论】:

    【解决方案3】:

    同意 Reed 的回答 (+1)。

    我要补充一点,如果您使用的是 .NET 4,您可能需要查看任务并行库来解决此类问题。针对这种情况,请查看Parallel.ForEach()

    【讨论】:

    • 虽然我喜欢 Parallel.ForEach - 意识到它本身就是一种阻塞方法,其中 OP 是“一劳永逸” - 所以使用它时存在功能差异。
    【解决方案4】:

    如果顺序无关紧要,那就去吧

    Parallel.ForEach(myList, obj => this.MyMethod(obj) );
    

    Write a Simple Parallel.ForEach Loop

    【讨论】:

    • Parallel.ForEach 本身是一种阻塞方法,其中 OP 是“即发即弃” - 所以使用它时存在功能差异。在这种情况下,这可能不合适(至少在不将其放入任务本身的情况下是不合适的)
    【解决方案5】:

    我更喜欢这种方式:

    public void ThreadingMethod()
    {
        var myList = new List<MyClass> { new MyClass("test1"), new MyClass("test2") };
    
    
    Parallel.ForEach(myList, new ParallelOptions() { MaxDegreeOfParallelism = 100 },
             (myObj, i, j) =>
             {
                 MyMethod(myObj);
             });
    
    }
    

    虽然没有测试....

    【讨论】:

    • Parallel.ForEach 的功能行为与 OP 的方法不同。原始代码具有“即发即弃”的行为,而这将阻塞调用线程,直到操作完成。此外,除非有特定原因,否则我不建议设置 MaxDegreeOfParallelism。
    • @ReedCopsey - 我猜你只是假设。使用 Parallel.ForEach 始终是在应用程序中实现正确线程的首选方法,特别是如果您有很多要处理的项目(同时)并且没有足够的资源来使用简单的 Thread()。如果他在您建议的方法中使用 1000 个线程,那么实现非常糟糕。
    • Parallel.ForEach 并不总是首选方法。这是一个不错的选择,但也是众多选择之一。使用 Tasks 或 ThreadPool 通常是比自己管理线程更好的选择,但即便如此,有时仍然首选直接使用 Thread。 OP 的代码中没有足够的信息表明 Parallel.ForEach 将是首选选项。 (当然,我几乎总是选择一个长时间运行的任务而不是手动管理的线程)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-30
    • 2012-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多