SaveFileDialog:由于多线程应用程序中的“owner”参数而导致InvalidOperationException

时间:2011-05-20 09:30:06

标签: c# .net winforms multithreading savefiledialog

对于长篇文章感到抱歉,但我试图解释这个问题非常详细,以免产生混淆。最后一句包含实际问题。

我正在使用C#/ .NET编写多线程应用程序。

该应用程序包含一个主窗口,可以显示来自压力传感器的数据。传感器数据是在自己的线程中获取的。

数据也记录在类ListView的实例中:

enter image description here

可以通过“保存”按钮将记录的数据保存到磁盘上的文件(应该打开.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,这样主窗口就会被阻塞(“无法点击”)而没有(线程)麻烦?

谢谢。

3 个答案:

答案 0 :(得分:2)

调试期间遇到的跨线程异常是Managed Debugging Assistant。它们通常不在调试器外部活动。这就解释了为什么在Visual Studio之外没有看到运行应用程序的原因。

看起来你已经发现自己根本无法对主UI空间以外的线程中的UI元素做任何事情。您可以使用ISynchronizeInvoke方法(即InvokeBeginInvoke)将操作的执行封送到UI线程,以便您可以安全地访问UI元素。

我仍然看到你的代码有问题。在工作线程上运行的OpenSaveFileDialog方法中,您正在调用SaveFileDiaglog的构造函数,当然,这是一个UI元素。你不能这样做。值得重复。您无法从工作线程向FormControl执行任何。这包括调用构造函数。

答案 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

只需两种方法即可完成!