3

对不起,很长的帖子,但我试图非常详细地解释这个问题,以免引起混淆。最后一句包含实际问题。

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

该应用程序由一个主窗口组成,该窗口可视化来自压力传感器的数据。传感器数据在自己的线程中获取。

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

在此处输入图像描述

可以通过“保存”按钮将记录的数据保存到磁盘上的文件(应该打开 .NET 类的实例SaveFileDialog)。

SaveFileDialog也在自己的线程中运行。现在调用方法时出现问题SaveFileDialog.ShowDialog()

System.InvalidOperationException 未处理 Message="Cross-thread operation not valid: Control 'tlpMain' 从创建它的线程以外的线程访问。" 源="系统.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);
    ...

InvalidOperationException仅在使用 Visual Studio 的调试器运行应用程序时引发/显示。到目前为止,“正常”运行应用程序时没有问题。

但我想避免这个问题。

我尝试构建一个包装方法(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="当前线程必须设置为单线程单元 (STA) 模式才能进行 OLE 调用。确保您的 Main 函数上标记了 STAThreadAttribute。仅当调试器是附加到过程中。” 源="系统.Windows.Forms"

有没有人知道如何以SaveFileDialog某种方式打开主窗口,以便在没有(线程)麻烦的情况下阻止主窗口(“不可点击”)?

谢谢你。

4

3 回答 3

2

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

看起来您自己发现您根本无法从主 UI 线程以外的线程对 UI 元素执行任何操作。您使用ISynchronizeInvoke方法,即Invokeor BeginInvoke,将操作的执行编组到 UI 线程上,以便您可以安全地访问 UI 元素。

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

于 2011-05-20T13:24:13.600 回答
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();
            ...
}

仍有优化空间(当然),但我对这个初步结果感到满意。

所以非常感谢你的帮助。

于 2011-05-23T12:59:12.300 回答
0

关注这篇微软博文:http: //blogs.msdn.com/b/smondal/archive/2011/05/11/10059279.aspx

只需两种方法,您就完成了!

于 2012-10-24T07:50:59.443 回答