Как стать автором
Обновить

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

Универсальный велосипед
public static class Execute
{
    private static Action<Action> executor = action => action();

    public static void InitializeWithSynchronizationContext()
    {
        var context = SynchronizationContext.Current;
        if (context == null)
        {
            throw new InvalidOperationException("Unable to initialize synchronization context");
        }

        executor = action =>
        {
            if (Equals(context, SynchronizationContext.Current))
            {
                action();
            }
            else
            {
                SendAction(context, action);
            }
        };
    }

    public static void OnUIThread(this Action action)
    {
        executor(action);
    }        

    private static void SendAction(SynchronizationContext context, Action action)
    {
        ExceptionDispatchInfo exceptionDispatchInfo = null;

        context.Send(state =>
        {
            try
            {
                action();
            }
            catch (Exception ex)
            {
                exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
            }
        }, null);

        if (exceptionDispatchInfo != null)
        {
            exceptionDispatchInfo.Throw();
        }
    }
}

Отдельное спасибо за ExceptionDispatchInfo — этот класс незаменим при написании велосипедов!
В рамках одного потока это работает – сообщения приходят, форма их получает, данные обновляются. При добавлении же еще нескольких, наша функция SendNotificationToForm начинает вести не совсем так, как ожидалось. Сообщения приходят и форма их получат, вот только складывает их в очередь для обновления контента и пока ваши background потоки не закончат работу, не спешит выводить на экран. И чем более сложной задачей заняты вы в других потоках, тем нагляднее это проявляется.
Не верю — никогда не сталкивался с подобным. Сообщения всегда приходили вовремя, сколько бы потоков я не создавал.

Однако, контексты синхронизации следует использовать хотя бы потому что их время жизни больше, чем время жизни формы (а время жизни дескриптора окна — меньше). С контекстами синхронизации вы никогда не столкнетесь с вылетом программы при закрытии окна, да и начинать операцию при открытии окна можно прямо из конструктора, а не из события HandleCreated.
К сожалению, пример собран на реальном коде. Несколько потоков, порожденных друг из друга и подписка на форме, где идет анализ callbacks и вывод полученных данных. По логам — данные до формы приходят штатно, а с визуализацией через BeginInvoke — проблема. Форма аккуратно складывает информацию у себя, затем ждет окончание работы потока и лишь после этого выводит накопленное.

При работе с контекстом — такого не происходит.
Я смотрел статьи на эту тему, народ предлагает для подобных случаев использовать именно SynchronizationContext.

PS. Повторюсь еще раз — речь именно про WinForm, не WPF.
Повторюсь еще раз — проблема у вас в коде. Форма никого не ждет, ибо не умеет.
Вы там случайно Invoke вместо BeginInvoke ни разу не написали? Или синхронное действие в потоку UI не выполнили?
Нет, не выполнял.

3 потока, порожденных из основного «цепочкой», затем форма стоит на ShowDialog. Потоки при этом заняты своими делами и кидают сообщения. Первый поток — сообщения от него форма выводит без задержек, остальные потоки — сообщения на форму приходят, выполняется BeginInvoke, после чего — вывода данных нет до момента окончания потока. Все callback с потоков оформлены как асинхронные.

Переписал посылку данных с callback через SynchronizationContext вместо BeginInvoke — все стало отрисовывать.
Боюсь разочаровать велосепедистов, но реализация методов контекста синхронизации WinForms, делает велосипеды бессмысленными с точки зрения «скорости»/«принудительности» и т.п.
Контекст просто инкапсулирует элемент управления и вызывает у него BeginInvoke, да удобно с точки зрения, времени жизни объекта и переносимости кода, но со всех других точек зрения, это самообман.
public override void Post(SendOrPostCallback d, object state)
    {
      if (this.controlToSendTo == null)
        return;
      this.controlToSendTo.BeginInvoke((Delegate) d, new object[1]
      {
        state
      });
    }
Нет. Контекст WinForms работает по-другому, он посылает оконное сообщение потоку, без использования дескриптора окна. В этом его главный плюс.
UPD: извиняюсь, немного напутал. Он использует дескриптор окна — но не текущего, а скрытого. Но это все равно сильно отличается от «просто инкапсулирует элемент управления».
А чем отличается то? На старте создаем пустой контрол, который потом используем для межпоточных вызовов. Контрол спрятан, вызов BeginInvoke спрятан, чем не пример инкапсуляции?
Тем, что он это делает не «просто», а давая
1) гарантию времени жизни скрытого контрола — он будет жить даже когда не открыто ни одной формы,
2) удобный способ получения экземпляра — достаточно вызвать SynchronizationContext.Current, в то время как создать контрол с нужными свойствами — куда сложнее.

Кроме того, он
3) реализует абстракцию контекста синхронизации, которая не ограничивается исключительно инкапсуляцией контролов.
Разговор шел, про скорость и прочие плюшки реализуемые контекстом, я просто показал, что внутри контекст есть все тот же вызов BeginInvoke. Что не отменяет его плюсы, описанные как мной, так и вами, но в рамках того, для чего он был использован в статье, его использование эквивалентно изначальному коду!
В рамках статьи — согласен. Но мне показалось, вы критикуете первый из комментариев к статье…
Да не, я просто обратил внимание на то, что с точки зрения «технологии» межпоточного вызова, разница между использованием контекста и вызова begininvoke у формы отсутствует.
Эх… счастлив автор, он еще на WP с сетью в фоновых потоках не работал :)
Глубже копать смысла нет, реализация в любом случае на базе обычной очереди сообщений, провисания UI из за множественных потоков быть не должно если у них приоритет не задран до небес. По сути любой код работающий в UI потоке работает в рамках сессии обработки сообщения из очереди, если никто не держит выполнение, все спланированные на выполнение задачи будут выполнены.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории