Диспетчер WPF.Invoke 'завис'

У меня есть несколько сложное приложение WPF, которое, кажется, «зависает» или застревает в вызове ожидания при попытке использовать диспетчер для вызова вызова в потоке пользовательского интерфейса.

Общий процесс:

  1. Обработка события щелчка на кнопке
  2. Создайте новый поток (STA), который: создает новый экземпляр презентатора и пользовательского интерфейса, затем вызывает метод Отключить
  3. Затем Disconnect устанавливает свойство в пользовательском интерфейсе под названием Имя.
  4. Затем установщик для Name использует следующий код для установки свойства:

    if (this.Dispatcher.Thread != Thread.CurrentThread)
    {
        this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate{
            this.Name = value; // Call same setter, but on the UI thread
        });
        return;
    }

    SetValue(nameProperty, value); // I have also tried a member variable and setting the textbox.text property directly.

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

Итак, есть ли что-то, что я делаю неправильно, чего мне не хватает, очевидно или нет, или есть лучший способ обратиться к потоку пользовательского интерфейса, чтобы установить это свойство (и другие)?

Редактировать: Решением было вызвать System.Windows.Threading.Dispatcher.Run () в конце делегата потока (например, там, где выполнялась работа) - Спасибо всем, кто помог.

@Matthew - на самом деле ничего "неоптимального" в BeginInvoke нет; если вам абсолютно не нужно обновление Теперь, это нормально. Тем не менее, вы должны быть немного осторожны с захваченными переменными (т.е. не меняйте «значение» после вызова BeginInvoke. Вообще.)

Marc Gravell 07.11.2008 07:57

@Matthew - вы вообще не присоединяетесь () к новой теме, не так ли? Это объяснило бы это ...

Marc Gravell 07.11.2008 07:58

@Marc Gravell - по памяти я в какой-то момент присоединялся к теме, но я не уверен, было ли поведение таким же, когда я не использовал это. Причина присоединения заключается в том, что я хотел заблокировать остальную часть приложения, пока работа не будет завершена, но, возможно, я могу использовать альтернативу.

Matthew Savage 10.11.2008 05:28
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
21
3
25 202
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Вызов синхронный - вам нужен Dispatcher.BeginInvoke. Кроме того, я считаю, что ваш пример кода должен переместить «SetValue» внутрь оператора «else».

Это похоже на тупик; это обычно происходит, если поток, вызывающий .Invoke, уже имеет блокировку / мьютекс / и т. д., которые поток пользовательского интерфейса должен завершить свою работу. Самый простой подход - использовать вместо этого BeginInvoke: в этом случае текущий поток может продолжать работать и (предположительно) вскоре снимет блокировку, позволяя пользовательскому интерфейсу получить ее. В качестве альтернативы, если вы можете определить блокировку, которая нарушает правила, вы можете намеренно снять ее на какое-то время.

Спасибо, Марк, это хорошее объяснение, однако я все еще не понимаю, почему вообще существует блокировка. Как вы сами и Пол BeginInvoke предположили, это был вариант, но не оптимальный, поэтому нет гарантии, что он завершится. Сумасшедшие странные ошибки ....

Matthew Savage 07.11.2008 03:12
Ответ принят как подходящий

Вы говорите, что создаете новый поток STA, работает ли диспетчер в этом новом потоке?

Я получаю от "this.Dispatcher.Thread! = Thread.CurrentThread", что вы ожидаете, что это будет другой диспетчер. Убедитесь, что он запущен, иначе он не будет обрабатывать свою очередь.

Кейт, это хорошее замечание. Я недостаточно знаком с диспетчером, но разве диспетчер окна уже не запущен? Поток STA используется для создания нового окна, однако, если мне нужно запустить диспетчер вручную, он объяснит, почему он не обрабатывается ...

Matthew Savage 11.11.2008 02:24

Если вы создаете STA самостоятельно, попробуйте вызвать Dispatcher.Run () после того, как вы покажете свое окно. Насколько я понимаю, диспетчер - это насос сообщений, и при создании нового потока пользовательского интерфейса он будет создавать диспетчер по запросу, если вы управляете созданием, вам придется вызывать диспетчер Run.

Keith 11.11.2008 12:20

Взгляните на этот пост: eprystupa.wordpress.com/2008/07/28/…

Keith 11.11.2008 12:21

У меня аналогичная проблема, и, хотя я все еще не уверен, каков ответ, я думаю, что ваш

 if (this.Dispatcher.Thread != Thread.CurrentThread)
{
    this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate{
        this.Name = value; // Call same setter, but on the UI thread
    });
    return;
}

следует заменить на

 if (this.Dispatcher.CheckAccess())
{
    this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate{
        this.Name = value; // Call same setter, but on the UI thread
    });
    return;
}

CheckAccess не будет отображаться в Intellisense, но он есть и предназначен для этой цели. Кроме того, я согласен с тем, что в целом вы хотите использовать BeginInvoke здесь, однако я обнаружил, что не получаю обновления пользовательского интерфейса, когда выполняю этот асинхронный режим. К сожалению, когда я делаю это синхронно, я получаю состояние тупика ...

Я думаю, вы имеете в виду, если (! This.Dispatcher.CheckAccess ())

У меня также зависание с Invoke, или, если я могу BeginInvoke, мой делегат не вызывается - похоже, все делает по книге :-(

Я знаю, что это старый поток, но вот другое решение.

Я только что исправил похожую проблему. Мой диспетчер работал нормально, так что ...

Мне пришлось показать ОКНО ОТЛАДКИ -> ПОТОК, чтобы идентифицировать все потоки, которые где угодно выполняют мой код.

Проверяя каждый из потоков, я быстро видел, какой поток вызвал тупик.

Это было несколько потоков, объединяющих оператор lock (locker) { ... } и вызовы Dispatcher.Invoke ().

В моем случае я мог бы просто изменить конкретный оператор lock (locker) { ... } и заменить его на Interlocked.Increment(ref lockCounter).

Это решило мою проблему, потому что удалось избежать тупика.

void SynchronizedMethodExample() {

    /* synchronize access to this method */
    if (Interlocked.Increment(ref _lockCounter) != 1) { return; }

    try {
    ...
    }
    finally {
        _mandatoryCounter--;
    }
}

Я думаю, это лучше показать с помощью кода. Рассмотрим этот сценарий:

Поток A делает это:

lock (someObject)
{
   // Do one thing.
   someDispatcher.Invoke(() =>
   {
      // Do something else.
   }
}

Поток B делает это:

someDispatcher.Invoke(() =>
{
   lock (someObject)
   {
      // Do something.
   }
}

На первый взгляд все может показаться прекрасным и модным, но это не так. Это приведет к тупиковой ситуации. Диспетчеры похожи на очереди для потока, и при работе с такими взаимоблокировками важно думать о них таким образом: «Какая предыдущая отправка могла заблокировать мою очередь?». Поток A войдет ... и будет отправлен под замком. Но что, если поток B входит в момент времени, когда поток A находится в коде с пометкой «Сделать что-то одно»? Что ж...

  • Поток A имеет блокировку на someObject и выполняет некоторый код.
  • Поток B теперь отправляется, и диспетчер попытается получить блокировку на someObject, заблокировав ваш диспетчер, поскольку поток A уже имеет эту блокировку.
  • Затем поток A поставит в очередь другой пункт отправки. Этот элемент никогда не будет запущен, потому что ваш диспетчер никогда не завершит обработку вашего предыдущего запроса; его уже заклинило.

Теперь у вас красивый тупик.

Спасибо за хорошее объяснение. Сэкономил мне часы работы. Я исправил это, не получая блокировки в вызовах диспетчера (что делает ваш поток B). Есть ли другое решение этой проблемы?

Heribert 05.06.2015 17:23

@Heribert Это зависит от кода, с которым вы работаете. Подобные взаимоблокировки очень зависят от приложения. Если вы столкнулись с ситуацией, подобной описанной выше, вы можете попытаться заблокировать вызовы вне диспетчера.

Alexandru 05.06.2015 17:47

вот что я сделал :) Еще раз спасибо за сообщение и ответ

Heribert 05.06.2015 20:55

Другие вопросы по теме