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;
}
所以我正在使用 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;
}