Оператор Plinq заблокирован внутри статического конструктора

#c# #.net #task-parallel-library #plinq

#c# #.net #задача-параллельная-библиотека #plinq

Вопрос:

Я столкнулся с такой ситуацией, когда следующий оператор plinq внутри статического конструктора заблокирован:

 static void Main(string[] args)
{
    new Blah();
}

class Blah
{
     static Blah()
     {
         Enumerable.Range(1, 10000)
            .AsParallel()
            .Select(n => n * 3)
            .ToList();
     }
}
  

Это происходит только тогда, когда конструктор статичен.
Кто-нибудь может мне это объяснить, пожалуйста.

Это ошибка TPL? Компилятор? Я?

Комментарии:

1. У вас есть скомпилированный двоичный файл Windows, чтобы я мог сравнить сгенерированный MSIL? Я не уверен, что это проблема только для библиотеки, и мне любопытно 🙂

2. вы можете загрузить код и двоичные файлы отсюда: cid-c152e66cc73fcce1.skydrive.live.com /…

Ответ №1:

Как правило, опасно вызывать потоковый код из статического конструктора. Чтобы гарантировать, что статический конструктор выполняется только один раз, среда CLR выполняет статический конструктор под блокировкой. Если поток, выполняющий статический конструктор, ожидает вспомогательный поток, существует риск того, что вспомогательному потоку по какой-либо причине тоже понадобится внутренняя блокировка CLR, и программа перейдет в тупик.

Вот более простой пример кода, который демонстрирует проблему:

 using System.Threading;
class Blah
{
    static void Main() { /* Won’t run because the static constructor deadlocks. */ }

    static Blah()
    {
        Thread thread = new Thread(ThreadBody);
        thread.Start();
        thread.Join();
    }

    static void ThreadBody() { }
}
  

Раздел 10.5.3.3 «Гонки и взаимоблокировки» спецификации ECMA CLI гарантирует следующее:

Сама по себе инициализация типа не должна создавать взаимоблокировку, если только какой-либо код, вызываемый из инициализатора типа (прямо или косвенно), явно не вызывает блокирующие операции.

Таким образом, инициализатор типа (т. Е. статический конструктор) не приведет к взаимоблокировке при условии, что никакая операция в статическом конструкторе не блокирует поток. Если статический конструктор блокируется, это может привести к взаимоблокировке.

Комментарии:

1. Это очень хороший момент. Спасибо за ответ. Это просто что-то не интуитивно понятное. По крайней мере, если он каким-то образом вызвал исключение…

Ответ №2:

Хотя уже была объяснена причина, по которой вы не хотели бы выполнять потоковую работу внутри статического конструктора, я подумал, что хотел бы добавить, что «правильный» способ сделать это вместо этого был бы со статическим Lazy<T> . Это также более эффективно, поскольку работа по генерации этих ресурсов будет отложена до тех пор, пока эти ресурсы действительно не понадобятся.

 class Blah
{
    // Supply factory method to generate the numbers, but actual generation will be deferred
    private static Lazy<List<int>> MyMagicNumbers = new Lazy<List<int>>(Blah.GenerateMagicNumbers);

    public void DoSomethingWithMagicNumbers()
    {
        // Call to Lazy<T>.Value will synchronize any calling threads until value is initially generated from the factory
        List<int> magicNumbers = Blah.MyMagicNumbers.Value;

        // ... do something here ...
    }

    private List<int> GenerateMagicNumbers()
    {
        return Enumerable.Range(1, 10000)
                          .AsParallel()
                          .Select(n => n * 3)
                          .ToList();
    }
}
  

Ответ №3:

Как бы то ни было, проблема не возникает в Mono:

 [mono] /tmp @ dmcs par.cs 
[mono] /tmp @ mono ./par.exe 
  

У вас есть скомпилированный двоичный файл Windows, чтобы я мог сравнить сгенерированный MSIL? Я не уверен, что это проблема только для библиотеки, и мне любопытно 🙂


Сравнение IL было немного запутанным, поэтому я решил просто попробовать оба двоичных файла на обеих платформах. Хехе, я восстановил свою старую виртуальную машину Windows, просто чтобы проверить это 🙂

Запуск скомпилированных двоичных файлов VS в Mono не является проблемой. Вы могли бы попробовать это в Windows, используя 2.10.1 (http://www.go-mono.com/mono-downloads/download.html ), всего 77,4 Мб 🙂

(Я использовал специально созданный mono 2.11 в Linux, так что, возможно, поддержка функций еще не завершена)

       run on platform:      MS.Net 4.0      Mono 2.1x
built on: ------------- ----------------------------------------
    Visual Studio       |      deadlock       no deadlock
                        |
    MonoDevelop         |      deadlock       no deadlock
  

Я также заметил, что при запуске в Windows CTRL-C может вырваться из блокировки.
Опубликую, если найду еще что-нибудь к этому.


Обновление 2

Что ж, установка Mono запускает круги вокруг установки VSExpress даже в Windows. Установка mono завершилась через 4 минуты и привела к:

 C:UsersSeth>"c:Program Files (x86)Mono-2.10.1binmono.exe" ConsoleApplication2.exe
C:UsersSeth>
  

Нет взаимоблокировки 🙂 Теперь все, что остается, это дождаться установки VSExpress (навсегда) и istall debugging tools (неизвестно), а затем взломать его (вероятно, до поздней ночи). CU позже

Комментарии:

1. вот двоичные файлы Windows: cid-c152e66cc73fcce1.skydrive.live.com /…

2. На данный момент я сдаюсь. Не удается запустить VSExpress на совершенно новом (никогда не запускаемом :)) Виртуальная машина Win7_64. Не удалось установить пакет обновления (?!), так что, увы, сегодня я не уделяю внимания WinDbg

3. спасибо за тяжелую работу. Похоже, это ошибка в реализации TPL для Windows.

4. Если взаимоблокировка является ожидаемым поведением, я удивляюсь, почему этого не происходит в mono. Разве они не блокируют статический конструктор?