【问题标题】:C# Application.DoEvents cannot work in my applicationC# Application.DoEvents 不能在我的应用程序中工作
【发布时间】:2014-02-12 09:31:20
【问题描述】:

由于我的英文不好,我把我的问题简单解释一下,在这里贴一个代码sn-p来描述问题。

问题是我们的 winForm 应用程序中的多线程问题。我将逻辑简化为以下代码示例。

在测试代码中,mainForm中有1个mainForm Form1和一个名为“开始”的按钮。当用户单击该按钮时,将从 2 个后台线程显示两个表单 form2 和 form3。 form2 关闭后,Form1 将被触发关闭。但是这里显示了form3,所以我需要用户自己关闭form3。所以我处理了 form.Closing 事件并使用 Application.DoEvents() 让用户关闭 form3。它看起来很有效。但实际上,form3 可以接受用户的操作,但 form3 不会按预期关闭。

这里请解释一下为什么form3不能关闭以及如何修改代码使用户的关闭操作生效。

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace CloseFormFromMainThread
{
public partial class Form1 : Form
{
    private Form2 _form2;
    private Form2 _form3;
    private SynchronizationContext _synchronizationContext;

    public Form1()
    {
        InitializeComponent();
        Closing += Form1Closing;
    }

    void Form1Closing(object sender, CancelEventArgs e)
    {
        while (_form3 != null)
        {
            Application.DoEvents();
            Thread.Sleep(100);
        }
    }

    private void ButtonStartClick(object sender, EventArgs e)
    {
        var thread = new Thread(StartForm3);
        thread.Start();

        var thread2 = new Thread(StartForm2);
        thread2.Start();
    }

    private void StartForm3()
    {
        Thread.Sleep(200);
        var action = new Action(() =>
                                    {
                                        _form3 = new Form2();
                                        _form3.Text = "form 3";
                                        _form3.ShowDialog();
                                        _form3 = null;
                                    });
        ExecuteActionInUiThread(action);
    }

    private void Form1Load(object sender, EventArgs e)
    {
        _synchronizationContext = SynchronizationContext.Current;
    }

    private void StartForm2()
    {
        Thread.Sleep(500);
        var action = new Action(() =>
        {
            _form2 = new Form2();
            _form2.Text = "form 2";
            _form2.ShowDialog();

            Close();
        });
        ExecuteActionInUiThread(action);
    }

    private void ExecuteActionInUiThread(Action action)
    {
        var sendOrPostCallback = new SendOrPostCallback(o => action());
        _synchronizationContext.Send(sendOrPostCallback, null);
    }
}
}

【问题讨论】:

  • 你为什么使用线程?一切似乎都在 UI 线程上运行?第 1 步是摆脱所有不必要的复杂性并删除线程。然后你就有机会看到你的程序真正做了什么。
  • 真正的代码不应该需要 Thread.Sleep()。并且 GUI 始终是 1 个线程,否则不要尝试创建它。
  • @HenkHolterman:这行不通。 WinForms 不支持多线程。它只会崩溃。
  • 这是一个非常简单的代码来描述我的问题。在我的应用程序中,UI 线程中有许多复杂的函数,这些函数会花费大量时间并阻塞用户的其他操作。我把函数移到了后台线程,但是代码中有一些UI操作,我使用synchronizationContext来同步主线程中的UI调用,比如form2和form3。有时,用户想关闭form2来退出我们的应用程序,我们需要用户先从其他功能关闭form3,然后关闭主窗体。我们不能在这里使用 Application.Exit。
  • @PMF:我就是这么说的,对吧?

标签: c# multithreading winforms synchronizationcontext


【解决方案1】:

第一个建议:不要使用 Application.DoEvents()。曾经。每当您认为需要它时,您的代码流中就有一个概念性问题,您应该首先解决它。我猜您的代码只是在创建死锁,因为它会等待 OnClosing 回调返回,然后才能处理更多事件(例如关闭另一个表单)。

【讨论】:

  • 如果我去掉Application.DoEvents(),如何让对话框接受用户的点击操作并处理关闭操作?
  • 您正在以模态模式打开对话框(使用ShowDialog())。这会自动运行消息循环并处理事件。
  • form3.Close()方法已经执行,但是form3.ShowDialog()没有返回。为什么?
  • Form3.Close 中发生了什么?您是否将Dialogresult 设置为适当的值?您是否有将 Cancel 设置为 true 的 OnClosing 回调?
  • 在 Form3.Close() 中什么都不做。根据我的检查,引发了 form3.Closing,但未引发相应的 Closed 事件。
【解决方案2】:

当我检查 Form.Close() 的源代码时,对于模态对话框,只引发了 FormClosing 事件,没有引发 Closed 事件。没有发送信号让 form3.ShowDialog 继续。所以我认为只使用主线程和 Application.DoEvents 不能让代码继续。那么就是主线程中的软死锁。

目前,我使用另一个线程进行检查 (_form3 != null) 并让主线程执行 _form3.ShowDialog() 逻辑。以下是我关于 Form1Closing 的代码。

    private bool _isFormClosing;
    void Form1Closing(object sender, CancelEventArgs e)
    {
        if (_form3 == null) return;

        e.Cancel = true;
        if (!_isFormClosing)
        {
            _isFormClosing = true;
            Task.Factory.StartNew((() =>
            {
                while (_form3 != null)
                {
                    Thread.Sleep(50);
                }

                ExecuteActionInUiThread(Close);
            }));
        }
    }

【讨论】:

  • 不要这样做。从后台线程打开表单是一个非常糟糕的主意,除非您正在创建像 Windows Explorer 这样的多线程 UI 应用程序,其中每个 UI 线程都相互独立。
猜你喜欢
  • 2011-07-14
  • 2014-07-29
  • 1970-01-01
  • 1970-01-01
  • 2011-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-18
相关资源
最近更新 更多