系统.显示WinForms对话框后主线程没有触发事件
本文关键字:事件 线程 显示 WinForms 对话框 系统 | 更新日期: 2023-09-27 18:18:34
我有一个WPF对话框,它显示了一个进度表控件,以及一个后台任务(System.Threading.Tasks.Task
),它提供了一个需要输入进度表的进度更新流。两者之间的中介是一个System.Progress<T>
对象。
在"正常"情况下,这一切都可以正常工作:
- 后台任务在某个非主线程的线程X上调用
System.IProgress.Report()
。 -
System.Progress
对象在切换线程时执行其内部魔法 - 进度表控件在主线程(拥有该控件)上更新
System.Progress
对象在主线程上触发ProgressChanged
事件。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
的替换?
EDIT:我可能会补充说,可执行文件是核心的MFC应用程序,.exe项目是用/CLR编译的,我正在查看的c#代码是通过c++/CLI调用的。c#代码是为。net框架4.5.1编译并运行的。复杂的设置是由于应用程序是具有现代态度的传统野兽:-),但到目前为止,这对我们来说工作得很好。
有趣的发现。默认情况下,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;