我有一个显示进度表控件的WPF对话框,以及一个后台任务(System.Threading.Tasks.Task
),它提供了需要输入进度表的进度更新流。两者之间的中介是System.Progress<T>
对象。
这一切都在“正常”情况下完美运作:
dist/dev_tool/langdetect/
。 System.IProgress.Report()
对象在切换线程时发挥其内在魔力System.Progress
对象在主线程上触发System.Progress
事件。现在,如果我打开任何 WinForms对话框,然后关闭它,然后启动我的后台任务,ProgressChanged
突然激活不在主线程上的System.Progress
事件,但在某些线程Y上,这不是主线程。这当然导致ProgressChanged
,因为事件处理程序尝试在与拥有该控件的线程不同的线程上更新WPF进度表控件。
我注意到InvalidOperationException
的文档说:
注册System.Progress
事件的[...]事件处理程序通过构造实例时捕获的
SynchronizationContext
实例调用。如果在构建时没有当前SynchronizationContext
,则将在ThreadPool
上调用回调。
这似乎与我能观察到的相匹配,因为这是ProgressChanged
在坏情况下触发事件时调用堆栈的下半部分的样子:
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
属性的值,但它永远不为null。属性返回的System.Progress
对象具有以下类型:
SynchronizationContext
System.Windows.Forms.WindowsFormsSynchronizationContext
不幸的是,我对WinForms没有多少经验,而System.Threading.SynchronizationContext
没有任何经验,所以我在这里发生了很多不知所措。
为什么打开WinForms对话框会更改SynchronizationContext
值?为什么这会对SynchronizationContext.Current
的行为产生影响?有没有办法如何“解决”问题,而不是编写我自己的System.Progress
替换?
编辑:我可能会补充一点,可执行文件是核心的MFC应用程序,.exe项目是用/ CLR编译的,我正在查看的C#代码是通过C ++ /调用的CLI。 C#代码是为.NET Framework 4.5.1编译的(并在其下运行)。复杂的设置是由于应用程序是一个具有现代态度的传统野兽:-),但到目前为止,这对我们来说非常好。
答案 0 :(得分:4)
有趣的发现。默认情况下,WindowsFormSynhronizationContext
会自动安装在任何Control
类(包括Form
)构造函数中,以及第一个消息循环中,并在最后一个消息循环后卸载。通常不会观察到此卸载行为,因为WinForms应用程序通常位于Application.Run
调用内。
但不是你的情况。通过以下简单的WF应用程序可以轻松复制该问题:
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class Program
{
[STAThread]
static void Main()
{
var form = new Form();
Trace.WriteLine(SynchronizationContext.Current?.GetType().ToString() ?? "null");
form.ShowDialog();
Trace.WriteLine(SynchronizationContext.Current?.GetType().ToString() ?? "null");
}
}
}
输出结果为:
System.Windows.Forms.WindowsFormsSynchronizationContext
System.Threading.SynchronizationContext
作为一种解决方法,我建议您在应用程序开始时手动设置主UI线程SynchronizationContext
,然后关闭AutoInstall
,这将阻止该卸载行为(但如果应用程序的其他部分替换主线程SynchronizationContext
):
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
WindowsFormsSynchronizationContext.AutoInstall = false;