Начало нового потока в цикле foreach - PullRequest
21 голосов
/ 23 февраля 2012

У меня есть список объектов, и я хотел бы перебрать этот список и начать новый поток, передавая текущий объект.

Я написал пример того, что, по моему мнению, должно делать это, но это не работает. В частности, кажется, что потоки перезаписываются на каждой итерации. Это на самом деле не имеет смысла для меня, потому что я каждый раз создаю новый объект 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));

но ни один из потоков не запустился.

Я думаю, что у меня просто неправильное представление о том, как должны работать потоки. Может ли кто-нибудь указать мне правильное направление и сказать, что я делаю неправильно?

Ответы [ 5 ]

42 голосов
/ 23 февраля 2012

Это потому, что вы закрываете переменную в неправильной области видимости. Решением здесь является использование временного в цикле foreach:

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

Для получения дополнительной информации, я рекомендую прочитать пост Эрика Липперта на эту тему: Закрытие переменной цикла, считающейся вредной

3 голосов
/ 23 февраля 2012

Проблема в том, что вы используете самое последнее значение объекта внутри вашего замыкания.Таким образом, каждый вызов потока ищет одно и то же значение.Чтобы обойти это, скопируйте значение в локальную переменную:

foreach(MyClass myObj in myList)
{
    MyClass localCopy = myObj;
    Thread myThread = new Thread(() => this.MyMethod(localCopy));
    myThread.Start();
}
2 голосов
/ 23 февраля 2012

Согласен с ответом Рида (+1).

Я бы добавил, что если вы работаете в .NET 4, вы можете обратиться к параллельной библиотеке задач для решения этого класса проблем. Специально для этого случая взгляните на Parallel.ForEach () .

1 голос
/ 23 февраля 2012

Я предпочитаю так:

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

}

не проверено, хотя ....

1 голос
/ 23 февраля 2012

если последовательность не имеет значения, перейдите к

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

Записать простой параллель. ForEach Loop

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...