У меня есть диалоговое окно WPF, в котором отображается индикатор выполнения и фоновая задача (System.Threading.Tasks.Task
), который предоставляет поток обновлений о ходе выполнения, которые необходимо передать в индикатор выполнения. Посредником между ними является объект System.Progress<T>
.
Все это отлично работает при «нормальных» обстоятельствах:
- Фоновая задача вызывает
System.IProgress.Report()
в некотором потоке X, который не является основным потоком. - Объект
System.Progress
выполняет свою внутреннюю магию, переключая потоки. - Объект
System.Progress
запускает событиеProgressChanged
в основном потоке. - Элемент управления индикатором прогресса обновляется в основном потоке (который владеет элементом управления)
Теперь, если я открою любое диалоговое окно WinForms, затем закрою его, а затем запущу фоновую задачу, System.Progress
внезапно вызовет событие ProgressChanged
не в основном потоке, а в каком-то потоке Y, который не является основным потоком. . Это, конечно, приводит к InvalidOperationException
, потому что обработчик событий пытается обновить элемент управления индикатором выполнения WPF в потоке, отличном от того, которому принадлежит элемент управления.
Я заметил, что документы для System.Progress
говорят, что:
[...] обработчики событий, зарегистрированные с событием
ProgressChanged
, вызываются черезSynchronizationContext
экземпляр захвачен при создании экземпляра. Если на момент конструкции, обратные вызовы будут вызываться дляThreadPool
. .
Кажется, это соответствует тому, что я могу наблюдать, потому что так выглядит нижняя часть стека вызовов, когда System.Progress
запускает свое событие в плохом случае:
[...]
bei System.Progress`1.InvokeHandlers(Object state)
bei System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
bei System.Threading.ThreadPoolWorkQueue.Dispatch()
bei System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
Я проверил значение свойства SynchronizationContext.Current
. во время создания объекта System.Progress
, но он никогда не равен нулю. Объект SynchronizationContext
, возвращаемый свойством, имеет следующие типы:
- Хороший случай (т.е. перед открытием диалогового окна WinForms): объект представляет собой
System.Windows.Forms.WindowsFormsSynchronizationContext
- Плохой случай (т.е. после открытия диалогового окна WinForms): объект представляет собой
System.Threading.SynchronizationContext
К сожалению, у меня не так много опыта работы с WinForms и вообще с SynchronizationContext
, поэтому я совершенно не понимаю, что здесь происходит.
Почему открытие диалогового окна WinForms изменяет значение SynchronizationContext.Current
? Почему это влияет на поведение System.Progress
? Есть ли способ «исправить» проблему, кроме написания собственной замены System.Progress
?
EDIT: я мог бы добавить, что исполняемый файл представляет собой приложение MFC по своей сути, проект .exe скомпилирован с помощью /CLR, а код C#, на который я смотрю, вызывается через C++/CLI. Код C# скомпилирован для (и работает под) .NET framework 4.5.1. Сложная настройка связана с тем, что приложение является устаревшим зверем с современными взглядами :-), но пока это сработало для нас очень хорошо.
WindowsFormsSynchronizationContext
автоматически удаляется, когда счетчик внутреннего цикла сообщений становится равным 0. Обычно этого не происходит в приложении WF, потому что всегда работает 1 основной цикл (Application.Run
), но с архитектурой вашего приложения... Понятия не имею, даже если вы обманете каким-то образом, для правильной работы действительно требуется поддержка цикла сообщений. - person Ivan Stoev   schedule 18.11.2016System.Progress
каждый раз, когда вы начинаете фоновую работу? Используете ли вы один и тот же экземплярSystem.Progress
на протяжении всей жизни приложения? - person Felix Castor   schedule 18.11.2016System.Progress
снова и снова, новый экземплярSystem.Progress
создается каждый раз непосредственно перед созданием фоновой задачи. Затем новый экземпляр подбирает то, чтоSynchronizationContext
является текущим. - person herzbube   schedule 18.11.2016System.Progress
, когда код, который должен выполняться в основном потоке, выполняется в каком-то другом потоке. Теперь я считаю, что сброс наSynchronizationContext
является основной причиной, которую я должен устранить, иначе в неожиданных углах всплывут новые злые дела. - person herzbube   schedule 18.11.2016WindowsFormsSynchronizationContext
, а затем восстановлением его после удаления (сSynchronizationContext.SetSynchronizationContext()
). Это похоже работает нормально, но, как я сказал в своем вопросе, у меня нет опыта работы с контекстами синхронизации. Могли бы вы сказать, что восстановление предыдущего контекста — это то, что можно сделать, не создавая дополнительных проблем? - person herzbube   schedule 18.11.2016WinMainCRTStartup()
, а такжеAfxWinMain()
в нижней части стека вызовов. - person herzbube   schedule 21.11.2016