Почему SynchronizationContext по умолчанию не фиксируется в консольном приложении? - PullRequest
0 голосов
/ 07 октября 2018

Я пытаюсь узнать больше о SynchronizationContext, поэтому я сделал это простое консольное приложение:

private static void Main()
{
    var sc = new SynchronizationContext();
    SynchronizationContext.SetSynchronizationContext(sc);
    DoSomething().Wait();
}

private static async Task DoSomething()
{
    Console.WriteLine(SynchronizationContext.Current != null); // true
    await Task.Delay(3000);
    Console.WriteLine(SynchronizationContext.Current != null); // false! why ?
}

Если я правильно понимаю, оператор await захватывает текущий SynchronizationContext, тогдаотправляет остаток асинхронного метода на него.

Однако, в моем приложении SynchronizationContext.Current равно нулю после await.Почему это так?

РЕДАКТИРОВАТЬ:

Даже когда я использую свой собственный SynchronizationContext, он не захватывается, хотя вызывается его функция Post.Вот мой SC:

public class MySC : SynchronizationContext
{
    public override void Post(SendOrPostCallback d, object state)
    {
        base.Post(d, state);
        Console.WriteLine("Posted");
    }
}

И вот как я его использую:

var sc = new MySC();
SynchronizationContext.SetSynchronizationContext(sc);

Спасибо!

Ответы [ 3 ]

0 голосов
/ 07 октября 2018

Для уточнения того, что уже было указано.

Класс SynchronizationContext, который вы используете в первом фрагменте кода, является реализацией по умолчанию , которая ничего не делает.

Во втором фрагменте кода вы создаете свой собственный MySC контекст.Но вам не хватает того, что действительно заставило бы его работать:

public override void Post(SendOrPostCallback d, object state)
{
    base.Post(state2 => {
        // here we make the continuation run on the original context
        SetSynchronizationContext(this); 
        d(state2);
    }, state);        
    Console.WriteLine("Posted");
}
0 голосов
/ 07 октября 2018

Слово «захват» слишком непрозрачно, слишком похоже на то, что это то, что предполагается для фреймворка.Вводит в заблуждение, поскольку это обычно происходит в программе, которая использует одну из реализаций SynchronizationContext по умолчанию.Как и , который вы получаете в приложении Winforms .Но когда вы пишете свое собственное, тогда фреймворк больше не помогает, и это становится вашей работой:

Асинхронная / ожидающая обработка дает контексту возможность запустить продолжение (код после ожидания) наконкретная тема.Это звучит тривиально, так как вы делали это раньше, но на самом деле это довольно сложно.Невозможно произвольно прервать код, выполняемый этим потоком, что может привести к ужасным ошибкам повторного входа.Поток должен помочь, он должен решить стандартную проблему производитель-потребитель .Принимает потокобезопасную очередь и цикл, который очищает эту очередь, обрабатывая запросы вызова.Задача переопределенных методов Post и Send состоит в том, чтобы добавлять запросы в очередь, а задача потока - использовать цикл для его очистки и выполнения запросов.

Основной поток Winforms, WPF илиВ приложении UWP есть такой цикл, он выполняется Application.Run ().С соответствующим SynchronizationContext, который знает, как передать его запросами на вызовы, соответственно WindowsFormsSynchronizationContext, DispatcherSynchronizationContext и WinRTSynchronizationContext.ASP.NET тоже может это сделать, использует AspNetSynchronizationContext.Все предоставлено фреймворком и автоматически установлено с помощью библиотеки классов сантехники.Они фиксируют контекст синхронизации в своем конструкторе и используют Begin / Invoke в своих методах Post и Send.

Когда вы пишете свой собственный SynchronizationContext, вы должны позаботиться об этих деталях.В своем фрагменте вы не переопределяли Post и Send, но унаследовали базовые методы.Они ничего не знают и могут выполнить запрос только в произвольном потоке потоков.Таким образом, SynchronizationContext.Current теперь равен нулю в этом потоке, поток пула потоков не знает, откуда поступил запрос.

Создание собственного не так сложно, ConcurrentQueue и делегаты помогают значительно сократить код,Многие программисты сделали это, эта библиотека часто цитируется.Но за это приходится платить серьезную цену: этот диспетчерский цикл в корне меняет поведение приложения в консольном режиме.Он блокирует поток, пока цикл не закончится.Точно так же, как Application.Run ().

Вам нужен совершенно другой стиль программирования, тот, который вы знакомы из приложения с графическим интерфейсом.Код не может занять слишком много времени, поскольку он запускает цикл диспетчера, предотвращая отправку запросов на вызов.В приложении с графическим интерфейсом, которое заметно заметно по тому, что пользовательский интерфейс перестает отвечать на запросы, в своем примере кода вы заметите, что ваш метод завершается медленно, так как продолжение не может работать некоторое время.Вам нужен рабочий поток для выделения медленного кода, бесплатного обеда нет.

Стоит отметить, почему этот материал существует.Приложения с графическим интерфейсом пользователя имеют серьезную проблему, их библиотеки классов никогда не являются поточно-ориентированными и не могут быть защищены с помощью lock.Единственный способ использовать их правильно - это делать все вызовы из одного потока.InvalidOperationException, когда вы этого не сделаете.Их диспетчерский цикл поможет вам сделать это, включив Begin / Invoke и async / await.Консоль не имеет этой проблемы, любой поток может что-то записать в консоль, и блокировка может помочь предотвратить смешивание их вывода.Таким образом, консольное приложение не должно нуждаться в пользовательском SynchronizationContext.YMMV.

0 голосов
/ 07 октября 2018

По умолчанию все потоки в консольных приложениях и службах Windows имеют только SynchronizationContext по умолчанию.

Kinldy для получения более подробной информации см. Ссылку https://msdn.microsoft.com/magazine/gg598924.aspx.Это имеет подробную информацию о SynchronizationContext в различных типах приложений.

...