2009-10-06 2 views
1

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

Так что код выглядит так, иш:

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; 
     } 
    } 
} 

ответ

8

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

всего две вещей, чтобы рассмотреть следующие вопросы:

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

EDIT: первый, у меня нет ссылок, потому что страницы MSDN на эту тему, к сожалению, очень мало подробностей - но я написал тестовые программы, чтобы проверить поведение BeginInvoke и все, что я пишу здесь, является результатом этих испытаний.

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

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

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

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

В WPF этот цикл и вся соответствующая обработка обрабатываются диспетчером.

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

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

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

НО

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

Какой код имеет контур сообщения?

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

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

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

Не могли бы вы немного рассказать о своем втором пункте, пожалуйста, что такое опасный сценарий? – mcintyre321

+0

Цитаты тоже были бы хороши. –

+1

Хорошо, я добавил короткий (всего в 6 раз больше, чем оригинальный ответ) объяснение того, как и когда Dispatcher. (Begin) Invoke работает. – Nir

0

Это интересный вопрос. Элементы, выполненные в диспетчере, помещаются в очередь и выполняются в той же теме как взаимодействие с пользовательским интерфейсом. Вот лучшая статья на эту тему: 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; 
      } 
    } 
} 

Но это, пожалуй, перебор :)

Смежные вопросы