В двух после отрывков первый безопасен, или необходимо ли сделать второй?
Сейфом я подразумеваю, что каждый поток, как гарантируют, назовет метод на Нечто от того же повторения цикла, в котором был создан поток?
Или необходимо ли скопировать ссылку на новую переменную, "локальную" для каждого повторения цикла?
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Thread thread = new Thread(() => f.DoSomething());
threads.Add(thread);
thread.Start();
}
-
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Foo f2 = f;
Thread thread = new Thread(() => f2.DoSomething());
threads.Add(thread);
thread.Start();
}
Обновление: Как указано в ответе Jon Skeet, это не имеет ничего конкретно, чтобы сделать с поточной обработкой.
Редактирование: это все изменяется в C# 5 с изменением туда, где переменная определяется (в глазах компилятора). От [1 119] C# 5 вперед, они - тот же .
<час>Перед C#5
второе безопасно; первое не.
С foreach
, переменная объявляется внешняя цикл - т.е.
Foo f;
while(iterator.MoveNext())
{
f = iterator.Current;
// do something with f
}
Это означает, что существует только 1 f
с точки зрения объема закрытия, и потоки могли бы очень вероятно запутаться - вызов метода многократно на некоторых экземплярах и нисколько на других. Можно зафиксировать это со вторым объявлением переменной в цикл:
foreach(Foo f in ...) {
Foo tmp = f;
// do something with tmp
}
Это затем имеет отдельное tmp
в каждом объеме закрытия, таким образом, нет никакого риска этой проблемы.
Вот простое доказательство проблемы:
static void Main()
{
int[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach (int i in data)
{
new Thread(() => Console.WriteLine(i)).Start();
}
Console.ReadLine();
}
Выводы (наугад):
1
3
4
4
5
7
7
8
9
9
Добавляют временную переменную, и она работает:
foreach (int i in data)
{
int j = i;
new Thread(() => Console.WriteLine(j)).Start();
}
(каждое число однажды, но конечно порядок не гарантируется)
Pop Catalin и ответы Marc Gravell корректны. Все, что я хочу добавить, является ссылкой на моя статья о закрытиях (который говорит и о Java и о C#). Просто мысль это могло бы добавить немного значения.
РЕДАКТИРОВАНИЕ: Я думаю, что стоит дать пример, который не имеет непредсказуемости поточной обработки. Вот короткая, но полная программа, показывающая оба подхода. "Плохое действие" список распечатывает 10 десять раз; "хорошее действие" перечисляет количества от 0 до 9.
using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
List<Action> badActions = new List<Action>();
List<Action> goodActions = new List<Action>();
for (int i=0; i < 10; i++)
{
int copy = i;
badActions.Add(() => Console.WriteLine(i));
goodActions.Add(() => Console.WriteLine(copy));
}
Console.WriteLine("Bad actions:");
foreach (Action action in badActions)
{
action();
}
Console.WriteLine("Good actions:");
foreach (Action action in goodActions)
{
action();
}
}
}
Ваша потребность использовать опцию 2, создавая закрытие вокруг переменной замены будет использовать значение переменной, когда переменная будет использоваться а не во время создания закрытия.
реализация анонимных методов в C# и его последствиях (часть 1)
реализация анонимных методов в C# и его последствиях (часть 2)
реализация анонимных методов в C# и его последствиях (часть 3)
Редактирование: для прояснения, в закрытиях C#" лексические закрытия " значение, что они не получают значение переменной, но саму переменную. Это означает, что при создании закрытия к переменной замены закрытие является на самом деле ссылкой на переменную не копия, он - значение.
Edit2: добавленные ссылки ко всем сообщениям в блоге, если кто-либо интересуется чтением о внутренностях компилятора.
Это - интересный вопрос, и кажется, что мы видели, что люди отвечают всеми различными способами. У меня создалось впечатление, что вторым путем будет единственный безопасный путь. Я хлестал реальное быстрое доказательство:
class Foo
{
private int _id;
public Foo(int id)
{
_id = id;
}
public void DoSomething()
{
Console.WriteLine(string.Format("Thread: {0} Id: {1}", Thread.CurrentThread.ManagedThreadId, this._id));
}
}
class Program
{
static void Main(string[] args)
{
var ListOfFoo = new List<Foo>();
ListOfFoo.Add(new Foo(1));
ListOfFoo.Add(new Foo(2));
ListOfFoo.Add(new Foo(3));
ListOfFoo.Add(new Foo(4));
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Thread thread = new Thread(() => f.DoSomething());
threads.Add(thread);
thread.Start();
}
}
}
при выполнении этого, Вы будете видеть, что опция 1 определенно не безопасна.
Foo f2 = f;
точки к той же ссылке как
f
, Таким образом, ничто не проиграло и ничто полученное...