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

问题描述

所以我正在使用WindowsAPICodepack中的CommonopenFileDialog。在我正在编写的应用程序的早期版本中,CommonopenFileDialog可以正常工作。在针对更高版本的.Net Framework的当前版本中,即使对话框是通过主窗体中的toolstripMenuItem click事件从主UI线程调用的,我也会得到跨线程操作无效的异常。以前曾经通过主窗体的按钮clickhandler以类似的方式调用它。

显式调用以相同形式显示CommonopenFileDialog的相同代码即可解决此问题,但通常的对话框行为会丢失。

这行得通,但是没有this。调用它不行。

   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时调用堆栈:

Stack frame when this.Invoke is called

编辑-这是ShowfolderChooser所做的(确定时返回true,取消时返回false):

 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

尽管问题不同,但原因是相同的:获取窗口句柄时出现问题。通过使用指定的当前表单的句柄调用对话框,在获取句柄时不再有跨线程访问。

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

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;
        }