CommonOpenFileDialog - 从 UI 线程调用后跨线程操作无效

CommonOpenFileDialog - Cross-thread operation not valid after call from UI thread

所以我正在使用 WindowsAPICodepack 中的 CommonOpenFileDialog。在我正在编写的应用程序的先前版本中,CommonOpenFileDialog 工作没有任何问题。在针对更高版本的 .Net Framework 的当前版本中,即使对话框是通过主窗体的 toolstripMenuItem 单击事件从主 UI 线程调用的,我也得到了 Cross-thread operation not valid 异常。之前它曾经以类似的方式从主窗体的按钮点击处理程序中调用。

在同一窗体上显式调用显示 CommonOpenFileDialog 的相同代码可以解决此问题,但通常的对话框行为会以这种方式丢失。

这行得通,但没有 this.Invoke 它行不通。

   private void loadWorkspaceToolStripMenuItem_Click(object sender, EventArgs e)
    {
        bool wait = true;
        this.Invoke((MethodInvoker)delegate ()
        {
            string startPath = LastUsedPath;
            if (FileFolderTools.ShowFolderChooser(ref startPath))
            {
                workspace.LoadWorkspace(startPath);
                LastUsedPath = startPath;
            }

            wait = false;
        });

        while(wait){}

    }

此外,虽然 while(wait) 存在,但 UI 仍然是响应式的,而通常情况下,当通过按下按钮进行阻塞调用时情况并非如此。不确定这里发生了什么...

编辑:底部更广泛的堆栈跟踪。

调用this.Invoke时的调用栈:

编辑 - 这就是 ShowfolderChooser 所做的(returns 正确时为真,取消时为假):

 public static bool ShowFolderChooser(ref string path){
        CommonOpenFileDialog dialog = new CommonOpenFileDialog();
        dialog.InitialDirectory = path;
        dialog.IsFolderPicker = true;
        CommonFileDialogResult res = dialog.ShowDialog();
        if (res == CommonFileDialogResult.Ok)
        {
            path = dialog.FileName; //set new path on OK
            return true;
        }
        return false;
    }

完全异常:

This exception was originally thrown at this call stack:
[External Code]
DataManagement.DataManagementCore.Helpers.FileFolderTools.ShowFolderChooser(ref string) in FileFolderTools.cs
Camera._Camera.btn_exportAll_Click(object, System.EventArgs) in Camera.cs
[External Code]
Camera.Program.Main() in Program.cs

但是等等...!还有更多...

所以我尝试将该代码放在一个单独的应用程序中,并且它可以完美地工作。所以我想这与我的应用程序有关。我看到的一个很大的区别是 loadWorkspaceToolStripMenuItem_Click 上的 Stack 框架有一个堆栈条目 user32.dll![Frames... 知道那个来自哪里吗?我认为这与此有关...

EDIT2 更广泛的堆栈跟踪:

at System.Windows.Forms.Control.get_Handle()
at Microsoft.WindowsAPICodePack.Dialogs.CommonFileDialog.ApplyNativeSettings(IFileDialog dialog)
at Microsoft.WindowsAPICodePack.Dialogs.CommonFileDialog.ShowDialog()
at Camera._Camera.btn_exportAll_Click(Object sender, EventArgs e) in D:\XXXXXXXXXXXXXX\TestProjects\DataManagementTestProject\Camera\Mainscreen\Camera.cs:line 661
at System.Windows.Forms.Control.OnClick(EventArgs e)
at System.Windows.Forms.Button.OnClick(EventArgs e)
at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ButtonBase.WndProc(Message& m)
at System.Windows.Forms.Button.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(Form mainForm)
at DataManagementTestProject.Program.Main() in D:\XXXXXXXXXXXXXX\TestProjects\DataManagementTestProject\Program.cs:line 20

从这个堆栈跟踪中,我可以确认调用是在 UI 的主线程上完成的,因为它应该由 MessageLoop 调用。似乎是 getHandle 有问题,某处出错了。但这在 API 包中,它应该可以工作...

所以经过几个小时的工作,并在 github 上挖掘了软件包的问题,​​我找到了答案 here

尽管问题不同,但原因相同:获取 window 句柄时出现问题。通过使用指定的当前表单句柄调用对话框,在获取句柄时不再有 cross-thread 访问权限。

最终解决方案:添加指向函数调用的指针。并将表单句柄传递给对话框。

string path = LastUsedPath;
if (FileFolderTools.ShowFolderChooser(ref path, this.Handle)){
      workspace.LoadWorkspace(path);
      LastUsedPath = path;
}
    public static bool ShowFolderChooser(ref string path, IntPtr handle)
        {
            CommonOpenFileDialog dialog = new CommonOpenFileDialog();
            dialog.InitialDirectory = path;
            dialog.IsFolderPicker = true;
            CommonFileDialogResult res = dialog.ShowDialog(handle);
            if (res == CommonFileDialogResult.Ok)
            {
                path = dialog.FileName; //set new path on OK
                return true;
            }
            return false;
        }