【发布时间】:2016-11-18 16:43:28
【问题描述】:
我有一个显示进度表控件的 WPF 对话框和一个后台任务 (System.Threading.Tasks.Task),它提供了需要馈入进度表的进度更新流。两者之间的中介是一个System.Progress<T> 对象。
这一切都在“正常”情况下完美运行:
- 后台任务在某个不是主线程的线程 X 上调用
System.IProgress.Report()。 -
System.Progress对象在切换线程时发挥其内在的魔力 -
System.Progress对象在主线程上触发ProgressChanged事件。 - 进度表控件在主线程(拥有该控件)上更新
现在,如果我打开 任何 WinForms 对话框,然后将其关闭,然后启动我的后台任务,System.Progress 会突然触发 ProgressChanged 事件,而不是在主线程上,而是在某个线程上Y 那不是主线程。这当然会导致InvalidOperationException,因为事件处理程序尝试在与拥有该控件的线程不同的线程上更新 WPF 进度表控件。
我注意到System.Progress 的文档说:
[...] 使用
ProgressChanged事件注册的事件处理程序是通过在构造实例时捕获的SynchronizationContext实例调用的。如果在构造时没有当前的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()
我在创建System.Progress 对象时检查了SynchronizationContext.Current 属性的值,但它永远不会为空。该属性返回的SynchronizationContext对象有以下几种类型:
- 好案例(即在打开 WinForms 对话框之前):对象是
System.Windows.Forms.WindowsFormsSynchronizationContext - 坏情况(即打开 WinForms 对话框后):对象是
System.Threading.SynchronizationContext
不幸的是,我对 WinForms 没有太多经验,对 SynchronizationContext 一点经验也没有,所以我很茫然。
为什么打开 WinForms 对话框会更改 SynchronizationContext.Current 值?为什么这会对System.Progress 的行为方式产生影响?除了写我自己的System.Progress替换之外,有没有办法“解决”这个问题?
编辑:我可能会补充一点,可执行文件是核心的 MFC 应用程序,.exe 项目是使用 /CLR 编译的,而我正在查看的 C# 代码是通过 C++/ 调用的命令行界面。 C# 代码针对 .NET 框架 4.5.1 编译(并在其下运行)。复杂的设置是由于应用程序是具有现代态度的传统野兽 :-),但到目前为止,这对我们来说效果很好。
【问题讨论】:
-
您是否在非主 UI 线程上显示对话框?
-
@IvanStoev 不,我在主 UI 线程上显示 WinForms 对话框。我还在主 UI 线程上显示 WPF 进度对话框。
-
看来你是对的。当内部消息循环计数变为 0 时,
WindowsFormsSynchronizationContext会自动卸载。这通常不会在 WF 应用程序中发生,因为总是有 1 个主循环在运行(Application.Run),但是对于您的应用架构......我不知道,即使你以某种方式欺骗它,它也确实需要消息循环支持才能正常工作。 -
那么在您开始后台工作的任何时候实例化
System.Progress的新实例是不可能的吗?您是否在应用程序的整个生命周期中使用相同的System.Progress实例? -
Hmya,它不可能猜到它实际上是 MFC 运行调度程序循环。拥有不少于 3 个 gui 类库,这些类库严重依赖于正确的调度程序来完成工作,可能会出现什么问题?是的,那个。
标签: c# .net multithreading winforms synchronizationcontext