对于长篇文章感到抱歉,但我试图解释这个问题非常详细,以免产生混淆。最后一句包含实际问题。
我正在使用C#/ .NET编写多线程应用程序。
该应用程序包含一个主窗口,可以显示来自压力传感器的数据。传感器数据是在自己的线程中获取的。
数据也记录在类ListView
的实例中:
可以通过“保存”按钮将记录的数据保存到磁盘上的文件(应该打开.NET类SaveFileDialog
的实例)。
此SaveFileDialog
也在自己的线程中运行。
现在调用方法SaveFileDialog.ShowDialog()
时出现问题:
System.InvalidOperationException未处理 Message =“跨线程操作无效:控制'tlpMain'从其创建的线程以外的线程访问。” 源= “System.Windows.Forms的”
问题出现是因为 SaveFileDialog 的所有者(主窗口)正在另一个线程中运行。
这是代码,它为SaveFileDialog()创建线程:
private void bSave_Click(object sender, EventArgs e)
{
Thread saveFileDialog = new Thread(OpenSaveFileDialog);
saveFileDialog.SetApartmentState(ApartmentState.STA);
saveFileDialog.Start();
}
方法OpenSaveFileDialog()的代码:
private void OpenSaveFileDialog()
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Text Files (*.txt)|*.txt|CSV (*.csv)|*.csv|All Files (*.*)|*.*";
saveFileDialog.FilterIndex = 0;
/* Call "ShowDialog" with an owner ("this.Parent") to achieve, so that
* the parent window is blocked and "unclickable".
*
* Danger of an "InvalidOperationException" because "this.Parent" control
* is running (was created) in another thread.
* But "this.Parent" should not be modified by this method call.
*/
DialogResult pressedButton = saveFileDialog.ShowDialog(this.Parent);
...
使用Visual Studio的调试器运行应用程序时,仅抛出/显示 InvalidOperationException 。到目前为止,没有问题 - “正常”运行应用程序。
但我想避免这个问题。
我尝试构建一个包装器方法(SaveFileDialog):
private void OpenSaveFileDialog()
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
...
SaveFileDialog(saveFileDialog, this.Parent);
}
包装方法:
private void SaveFileDialog(SaveFileDialog saveFileDialog, Control owner)
{
if (owner.InvokeRequired)
BeginInvoke(new dSaveFileDialog(SaveFileDialog), new object[] { saveFileDialog, owner });
else
{
DialogResult pressedButton = saveFileDialog.ShowDialog(owner);
...
虽然TargetInvocationException
方法标有Main()
,但这会产生[STAThreadAttribute]
:
InnerException:System.Threading.ThreadStateException Message =“在进行OLE调用之前,必须将当前线程设置为单线程单元(STA)模式。确保Main函数上标记了STAThreadAttribute。仅当调试器附加到进程时才会引发此异常。” 源= “System.Windows.Forms的”
有没有人知道如何在某种程度上打开SaveFileDialog
,这样主窗口就会被阻塞(“无法点击”)而没有(线程)麻烦?
谢谢。
答案 0 :(得分:2)
调试期间遇到的跨线程异常是Managed Debugging Assistant。它们通常不在调试器外部活动。这就解释了为什么在Visual Studio之外没有看到运行应用程序的原因。
看起来你已经发现自己根本无法对主UI空间以外的线程中的UI元素做任何事情。您可以使用ISynchronizeInvoke
方法(即Invoke
或BeginInvoke
)将操作的执行封送到UI线程,以便您可以安全地访问UI元素。
我仍然看到你的代码有问题。在工作线程上运行的OpenSaveFileDialog
方法中,您正在调用SaveFileDiaglog
的构造函数,当然,这是一个UI元素。你不能这样做。值得重复。您无法从工作线程向Form
或Control
执行任何。这包括调用构造函数。
答案 1 :(得分:1)
很抱歉迟到的回复。
首先感谢您的快速和有益的回复。
不可能的提示
对表单或控件执行任何操作 工人线程
给了我很多帮助。
我通常不会为微软的Windows做GUI编程,所以我对它不太熟悉。
所以我重新考虑了之前的源代码,因为我想解决实际问题 (不是从工作线程做GUI事情)并希望有一个干净的逻辑代码结构。
因此,我已经阅读了Window的组件对象模型(COM)和使用的线程模型的主题:
现在代码如下:
主窗口(“UI线程”)在ApartmentState STA
...
ThreadStart threadStart = delegate { RunMainWindow(mainWindow); };
Thread mainWindowThread = new Thread(threadStart);
mainWindowThread.SetApartmentState(ApartmentState.STA);
mainWindowThread.Start();
...
“保存”按钮事件处理程序(主窗口):
private void bSave_Click(object sender, EventArgs e)
{
OpenSaveFileDialog();
}
方法“ OpenSaveFileDialog ”(主窗口):
private void OpenSaveFileDialog()
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
...
DialogResult pressedButton = saveFileDialog.ShowDialog();
...
}
仍有优化空间(当然),但我对此感到满意 - 初步结果。
非常感谢你的帮助。
答案 2 :(得分:0)
关注此微软博文:http://blogs.msdn.com/b/smondal/archive/2011/05/11/10059279.aspx
只需两种方法即可完成!