Короткий вариант: никогда не блокируйте внутри конструктора, и особенно не в конструкторе static
.
В вашем примере разница заключается в использовании анонимного метода. В первом случае вы захватили локальную переменную, которая заставляет анонимный метод компилироваться в свой собственный класс. Но во втором случае нет захвата переменной, и поэтому достаточно метода static
. За исключением того, что статический метод помещается в класс Program
. Который все еще инициализируется.
Таким образом, вызов анонимного метода блокируется инициализацией класса (вы не можете, из потока, отличного от того, где выполняется статический конструктор, выполнить метод в классе до тех пор, пока этот класс не завершит инициализация), и инициализация класса блокируется выполнением анонимного метода (метод ForAll()
не вернется, пока все эти методы не выполнили).
Тупик.
Трудно понять, какое хорошее предложение может быть выполнено, учитывая, что пример (как и ожидалось) упрощенной версии того, что вы действительно делаете. Но суть в том, что вы не должны делать длительные вычисления в статическом конструкторе. Если это достаточно медленный алгоритм, который оправдывает использование ForAll()
, то он достаточно медленный, чтобы он действительно не должен был быть частью инициализации класса в первую очередь.
Среди множества возможных вариантов решения проблемы можно выбрать класс Lazy<T>
, который упрощает отсрочку инициализации до тех пор, пока она не понадобится.
Например, предположим, что ваш параллельный код не просто выписывает элементы списка, но фактически обрабатывает их каким-то образом. То есть это часть фактической инициализации списка. После этого вы можете заключить, что инициализация в фабричным методом, выполняемой Lazy<T>
по требованию, а не в статическом конструкторе:
public class Program
{
public static void Main()
{
Console.WriteLine("foo");
}
private static readonly Lazy<List<int>> _list = new Lazy<List<int>>(() => InitList());
private static List<int> InitList()
{
var list = new List<int>() { 1,2,3 };
list.AsParallel().WithDegreeOfParallelism(4).ForAll(item => { Console.WriteLine(item.ToString()); });
return list;
}
}
Тогда код инициализации даже не будет выполняться на всех, пока какой-то код не требуется для доступа к списку , который он может сделать через _list.Value
.
Это тонко отличается от того, что я чувствовал, что это оправдывает новый ответ (т.вид использования анонимного метода изменяет поведение), но есть, по крайней мере, два других очень тесно связаны вопросы и ответы на переполнение стека:
Plinq statement gets deadlocked inside static constructor
Task.Run in Static Initializer
Как и в сторону: я недавно узнал, что с новым компилятором Roslyn они изменили, как они реализуют анонимные методы в этом сценарии, и даже те, которые могут быть статическими методами, создаются методами экземпляра в отдельном классе (если я правильно помню). Я не знаю, было ли это уменьшать распространенность такого рода ошибок или нет, но это, безусловно, изменило бы поведение (и устранило бы анонимный метод как источник тупика и hellip, конечно, всегда можно было бы воспроизвести проблему с вызов явно объявленного статического, именованного метода).
Это может быть связано с этим: http://stackoverflow.com/questions/15143931/strange-behaviour-of-console-readkey-with-multithreading - вы вызываете Console.WriteLine() в фоновом потоке, прежде чем были правильно инициализированы. –
Вам повезло с 'sb.AppendLine', метод не является потокобезопасным, если у вас было больше и больше строк, ваши строки были бы неверными или некоторые даже отсутствовали бы. –
Для меня он блокируется даже без 'Console.WriteLine' внутри статического конструктора' Program'. Вызов 'ForAll' (с пустым телом) в cctor вызывает зависание. (Targeting 4.5.2, VS 2013) – xxbbcc