Делает ли использование dispatcher.Invoke мой поток безопасным? - PullRequest
1 голос
/ 06 октября 2009

В моем приложении WPF запущена длительная загрузка, которая вызывает события хода выполнения, которые обновляют индикатор выполнения. У пользователя также есть шанс отменить загрузку, иначе он может пойти не так. Все это асинхронные события, поэтому их необходимо выполнить с помощью Dispatcher.Invoke, чтобы обновить пользовательский интерфейс.

Итак, код выглядит так, ish:

void OnCancelButtonClicked(object sender, EventArgs e)
{
    upload.Cancel();
    _cancelled = true;
    view.Close();
    view.Dispose();
}

void OnProgressReceived(object sender, EventArgs<double> e)
{
    Dispatcher.Invoke(() => 
    {
        if (!cancelled)
            view.Progress = e.Value;
    }
}

Предположим, что установка view.Progress на удаленном представлении выдает ошибку, безопасен ли этот поток кода? т. е. если пользователь нажимает кнопку отмены во время обновления прогресса, ему / ей придется ждать, пока прогресс не будет обновлен, и если прогресс обновляется во время выполнения OnCancelButtonClicked, вызов Dispatcher.Invoke вызовет обновление view.Progress для быть в очереди до тех пор, пока _cancelled установлен, поэтому у меня не будет проблем там.

Или мне нужен замок для безопасности, а-ля:

object myLock = new object();

void OnCancelButtonClicked(object sender, EventArgs e)
{
    lock(myLock)
    {
        upload.Cancel();
        _cancelled = true;
        view.Close();
        view.Dispose();
    }
}

void OnProgressReceived(object sender, EventArgs<double> e)
{
    Dispatcher.Invoke(() => 
    {
        lock(myLock)
        {
            if (!cancelled)
                view.Progress = e.Value;
        }
    }
}

Ответы [ 2 ]

8 голосов
/ 07 октября 2009

Вам не нужно добавлять блокировку. Запросы Dispatcher.Invoke и BeginInvoke не будут выполняться в середине другого кода (в этом их суть).

Просто две вещи для рассмотрения:

  1. BeginInvoke может быть более подходящим в этом случае, Invoke поставит в очередь запрос и затем заблокирует вызывающий поток, пока поток пользовательского интерфейса не станет свободным и не завершит выполнение кода, BeginInvoke будет только ставить запрос в очередь без блокировки.
  2. Некоторые операции, особенно операции, которые открывают окна (в том числе окна сообщений) или осуществляют межпроцессное взаимодействие, могут разрешить выполнение операций диспетчера очереди.

РЕДАКТИРОВАТЬ: во-первых, у меня нет ссылок, потому что на страницах MSDN по этому вопросу, к сожалению, очень мало деталей, - но я написал тестовые программы для проверки поведения BeginInvoke, и все, что я пишу здесь, является результатом этих тестов .

Теперь, чтобы перейти ко второму пункту, нам сначала нужно понять, что делает диспетчер. Очевидно, это очень упрощенное объяснение.

Любой пользовательский интерфейс Windows работает путем обработки сообщений; Например, когда пользователь наводит указатель мыши на окно, система отправит этому окну сообщение WM_MOUSEMOVE.

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

В основе каждой программы Windows есть цикл, называемый «цикл сообщений» или «насос сообщений», этот цикл читает следующее сообщение из очереди и вызывает код соответствующего окна для обработки этого сообщения.

В WPF этот цикл и вся связанная с ним обработка обрабатываются Диспетчером.

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

Dispatcher.Invoke и BeginInvoke работают, ставя запрошенную операцию в очередь и выполняя ее в следующий раз, когда поток возвращается в цикл сообщений.

Именно поэтому Dispatcher. (Begin) Invoke не может «внедрить» код в середину вашего метода, вы не вернетесь к циклу сообщений, пока ваш метод не вернется.

НО

Любой код может запустить цикл сообщений. Когда вы вызываете все, что выполняет цикл обработки сообщений, будет вызван Dispatcher, и он может выполнять операции (Begin) Invoke.

Какой код имеет цикл сообщений?

  1. Все, что имеет графический интерфейс или принимает пользовательский ввод, например, диалоговые окна, окна сообщений, перетаскивание и т. Д. - если бы в них не было цикла сообщений, приложение не отвечало бы и не могло обрабатывать пользовательский ввод.
  2. Межпроцессное взаимодействие, которое использует сообщения Windows за сценой (большинство методов межпроцессного взаимодействия, включая COM, используют их).
  3. Все остальное, что занимает много времени и не останавливает систему (то, что система не зависает, является доказательством того, что она обрабатывает сообщения).

Итак, подведем итог:

  • Диспетчер не может просто вставить код в ваш поток, он может выполнять код только тогда, когда приложение находится в «цикле сообщений».
  • Любой написанный вами код не имеет циклов сообщений, если вы не написали их явно.
  • Большая часть кода пользовательского интерфейса не имеет собственного цикла сообщений, например, если вы вызываете Window.Show, а затем выполняете некоторые длительные вычисления, окно появится только после того, как вычисление завершено, и метод возвращается (и приложение возвращается к цикл обработки сообщений и обрабатывает все сообщения, необходимые для открытия и рисования окна).
  • Но любой код, который взаимодействует с пользователем до его возврата (MessageBox.Show, Window.ShowDialog), должен иметь цикл обработки сообщений.
  • Некоторый код связи (сетевой и межпроцессный) использует циклы сообщений, а некоторые нет, в зависимости от конкретной используемой вами реализации.
0 голосов
/ 06 октября 2009

Это интересный вопрос. Элементы, выполняемые в диспетчере, ставятся в очередь и выполняются в том же потоке , что и взаимодействие с пользовательским интерфейсом. Вот лучшая статья на эту тему: http://msdn.microsoft.com/en-us/library/ms741870.aspx

Если бы я рискнул предположить, я бы сказал, что Dispatcher.Invoke (Action), вероятно, ставит в очередь атомарный рабочий элемент , поэтому, скорее всего, все будет в порядке, однако я не уверен, если оборачивает ваш обработчик событий пользовательского интерфейса в элементарный элемент действия, например:

//Are these bits atomic?  Not sure.
upload.Cancel();
_cancelled = true;

Ради безопасности я бы лично заблокировал, но ваш вопрос требует дополнительных исследований. Может понадобиться погружение в отражатель, чтобы понять наверняка.

Кстати, я бы, вероятно, немного оптимизировал вашу блокировку.

Dispatcher.Invoke(() => 
{
     if (!cancelled)
     {
          lock(myLock)
          {
               if(!cancelled)
                    view.Progress = e.Value;
          }
     }
}

Но это, вероятно, излишне:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...