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

发布于 2024-11-08 23:37:14 字数 2603 浏览 0 评论 0原文

很抱歉这篇文章很长,但我试图非常详细地解释这个问题,以免引起混乱。最后一句包含实际问题。

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

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

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

输入图像描述">

这个 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);
        ...

尽管 Main() 方法标有 [STAThreadAttribute]:

内部异常:System.Threading.ThreadStateException Message="在进行 OLE 调用之前,当前线程必须设置为单线程单元 (STA) 模式。确保您的 Main 函数上标记有 STAThreadAttribute。仅当调试器附加到进程时才会引发此异常。" 源=“System.Windows.Forms”

是否有人知道如何以某种方式打开SaveFileDialog,以便主窗口被阻止(“不可点击”)而不会遇到(线程)麻烦?

谢谢。

Sorry for the long post, but I tried to explain the problem very detailed so that no confusion should arise. The last sentence contains the actual question.

I'm programming a multi-thread application with C#/.NET.

The application consists of a main window, which visualizes data, coming from a pressure sensor. The sensor data is acquired in an own thread.

The data is also logged in an instance of class ListView:

enter image description here

There is the possibility to save the logged data to file on disk via a "Save" button (should open an instance of .NET class SaveFileDialog).

This SaveFileDialog is also running in an own thread.
Now there's a problem when calling the method SaveFileDialog.ShowDialog():

System.InvalidOperationException was unhandled
Message="Cross-thread operation not valid: Control 'tlpMain' accessed from a thread other than the thread it was created on."
Source="System.Windows.Forms"

The problem arises because the owner (the main window) of the SaveFileDialog is running in another thread.

Here's the code, which creates the thread for the SaveFileDialog():

private void bSave_Click(object sender, EventArgs e)
{
    Thread saveFileDialog = new Thread(OpenSaveFileDialog);
    saveFileDialog.SetApartmentState(ApartmentState.STA);
    saveFileDialog.Start();
}

Code for method 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);
    ...

The InvalidOperationException is only thrown/displayed when running the application with Visual Studio's debugger. It is no problem - so far - when running the application "normally".

But I would like to avoid this problem.

I tried to build a wrapper method (SaveFileDialog):

private void OpenSaveFileDialog()
{
    SaveFileDialog saveFileDialog = new SaveFileDialog();
    ...
    SaveFileDialog(saveFileDialog, this.Parent);
}

Wrapper method :

private void SaveFileDialog(SaveFileDialog saveFileDialog, Control owner)
{
    if (owner.InvokeRequired)
        BeginInvoke(new dSaveFileDialog(SaveFileDialog), new object[] { saveFileDialog, owner });
    else
    {
        DialogResult pressedButton = saveFileDialog.ShowDialog(owner);
        ...

This leads to a TargetInvocationException although the Main() method is labeled with [STAThreadAttribute]:

InnerException: System.Threading.ThreadStateException
Message="Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it. This exception is only raised if a debugger is attached to the process."
Source="System.Windows.Forms"

Does anybody have a clue how to open the SaveFileDialog in a way, so that the main window will be blocked ("unclickable") without having the (thread) trouble?

Thank you.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

杯别 2024-11-15 23:37:14

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

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

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

The cross-thread exception you get during debugging is a Managed Debugging Assistant. They are not normally active outside of the debugger. That explains why you do not see when you run the application outside of Visual Studio.

It looks like you have discovered on your own that you simply cannot do anything to a UI element from a thread other than the main UI thread. You use the ISynchronizeInvoke methods, namely Invoke or BeginInvoke, to marshal the execution of an operation onto the UI thread so that you can safely access UI elements.

I still see a problem with your code though. In the OpenSaveFileDialog method, which is running on the worker thread, you are calling the constructor for SaveFileDiaglog which, of course, is a UI element. You just cannot do this. It is worth repeating. You cannot do anything to a Form or Control from a worker thread. That includes calling the constructor.

云朵有点甜 2024-11-15 23:37:14

抱歉回复晚了。

首先感谢您的快速且有用的回复。

不可能的秘诀

对表单或控件执行任何操作
一个工作线程

对我帮助很大。

我通常不为微软的Windows进行GUI编程,所以我不太熟悉它。

所以我重新考虑了之前的源码,因为我想解决实际问题
(不从工作线程执行 GUI 操作)并且希望有一个干净且逻辑的代码结构。

因此,我阅读了 Window 组件对象模型 (COM) 和使用的线程模型的主题:

现在代码如下所示:

主窗口(“UI 线程”)在 ApartmentState STA

...
ThreadStart threadStart = delegate { RunMainWindow(mainWindow); };
Thread mainWindowThread = new Thread(threadStart);

mainWindowThread.SetApartmentState(ApartmentState.STA);
mainWindowThread.Start();
...

“保存”按钮 事件处理程序(main窗口):

private void bSave_Click(object sender, EventArgs e)
{
            OpenSaveFileDialog();
}

方法“OpenSaveFileDialog”(主窗口):

private void OpenSaveFileDialog()
{
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            ...

            DialogResult pressedButton = saveFileDialog.ShowDialog();
            ...
}

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

非常感谢您的帮助。

Sorry for the late reply.

First of all thank you for your quick and helpful responses.

The tip that's not possible

do anything to a Form or Control from
a worker thread

helped me a lot.

I usually not doing GUI programming for Microsoft's Windows and so I'm not so familiar with it.

So I reconsidered the previous source code because I wanted to solve the actual problem
(not doing GUI things from a worker thread) and would like to have a clean and logical code structure.

Therefore I've read in the topics of Window's Component Object Model (COM) and the used threading model:

Now the code looks like this:

The main window ("UI thread") is started in ApartmentState STA

...
ThreadStart threadStart = delegate { RunMainWindow(mainWindow); };
Thread mainWindowThread = new Thread(threadStart);

mainWindowThread.SetApartmentState(ApartmentState.STA);
mainWindowThread.Start();
...

"Save" button event handler (main window):

private void bSave_Click(object sender, EventArgs e)
{
            OpenSaveFileDialog();
}

Method "OpenSaveFileDialog" (main window):

private void OpenSaveFileDialog()
{
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            ...

            DialogResult pressedButton = saveFileDialog.ShowDialog();
            ...
}

There is still space for optimizations (for sure), but I'm comfortable with this - preliminary - result.

So thanks a lot for your help.

寒冷纷飞旳雪 2024-11-15 23:37:14

请关注此 Microsoft 博客文章:http://blogs.msdn。 com/b/smondal/archive/2011/05/11/10059279.aspx

只需两种方法即可完成!

Follow this microsoft blogpost: http://blogs.msdn.com/b/smondal/archive/2011/05/11/10059279.aspx

Just two methods and you are done!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文