【问题标题】:Thread safe form manipulation between two forms (WinForms C#)两个表单之间的线程安全表单操作 (WinForms C#)
【发布时间】:2011-10-03 21:50:20
【问题描述】:

我有两种形式,一种是主形式,另一种是作为模式对话框弹出的。从主窗体中产生的进程中,我想动态更新模态对话框上的文本。这是我所拥有的:

在主窗体中,我这样做:

// show the wait modal
            var modal = new WaitDialog { Owner = this };

            // thread the packaging
            var thread = new Thread(() => Packager.PackageUpdates(clients, version, modal));
            thread.Start();

            // hopefully it worked ...
            if (modal.ShowDialog() != DialogResult.OK)
            {
                throw new Exception("Something failed, miserably.");
            }

PackageUpdates 方法采用模态对话框,并执行以下操作:

 // quick update and sleep for a sec ...
             modal.SetWaitLabelText("Downloading update package...");
             Thread.Sleep(2000);

             modal.SetWaitLabelText("Re-packaging update...");

为了线程安全,我在模态对话框中执行此操作:

public void SetWaitLabelText(string text)
        {
            if (lblWaitMessage.InvokeRequired)
            {
                Invoke(new Action<string>(SetWaitLabelText), text);
            }
            else
            {
                lblWaitMessage.Text = text;
            }
        }

一切都很好......大部分时间。模态框每弹出三到四次,我就会在 lblWaitMessage.Text = text; 上得到一个异常,并且它没有调用命令。

我是否在此设置中遗漏了什么?

【问题讨论】:

  • 你启动线程太快了。等待 modal.Load 事件。
  • 就像 Hans Passant 说的,我不确定 thread.Start() 是否可以在 modal.ShowDialog() 之前安全使用。究竟是什么异常?
  • 您得到的确切异常是什么。正如 Hans 所说,很可能在表单完全构建之前,线程正在执行对SetWaitLabelText 的第一次调用。
  • 自从我发布此消息后,我再也没有收到异常。我会继续尝试并发布异常情况,并尝试 Hans 的解决方案。

标签: c# winforms


【解决方案1】:

就像@Hans Passant 指出的那样,您应该等待 modal.Load 事件。一个不错的选择是使用ManualResetEvent 通知您的线程等待直到发生这种情况。

WaitOne 方法将阻塞线程,直到调用 Set 方法。这是一个非常简单的设置,应该可以解决问题。

public partial class Form1 : Form
{
    ManualResetEvent m_ResetEvent;

    public Form1()
    {
        InitializeComponent();

        m_ResetEvent = new ManualResetEvent(false);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Dialog d = new Dialog { Owner = this, ResetEvent = m_ResetEvent };

        var thread = new Thread(new ParameterizedThreadStart(DoSomething));
        thread.Start(d);

        if (d.ShowDialog() != System.Windows.Forms.DialogResult.OK)
        {
            throw new Exception("Something terrible happened");
        }

    }

    private void DoSomething(object modal)
    {
        Dialog d = (Dialog)modal;     

        // Block the thread!
        m_ResetEvent.WaitOne();

        for (int i = 0; i < 1000; i++)
        {
            d.SetWaitLabelText(i.ToString());
            Thread.Sleep(1000);
        }
    }
}

这里是模态形式

public partial class Dialog : Form
{
    public Form Owner { get; set; }

    public ManualResetEvent ResetEvent { get; set; }

    public Dialog()
    {
        InitializeComponent();
    }

    public void SetWaitLabelText(string text)
    {
        if (label1.InvokeRequired)
        {
            Invoke(new Action<string>(SetWaitLabelText), text);
        }
        else
        {
            label1.Text = text;
        }
    }

    private void Dialog_Load(object sender, EventArgs e)
    {
        // Set the event, thus unblocking the other thread
        ResetEvent.Set();
    }
}

【讨论】:

    【解决方案2】:

    我认为你应该重写代码,让 thread.Start() 在 modal.ShowDialog() 之前不被调用。

    作为一种解决方法,您可以试试这个:

    public void SetWaitLabelText(string text) {
        Invoke(new Action<string>(SetWaitLabelText2), text);
    }
    
    void SetWaitLabelText2(string text) {
        lblWaitMessage.Text = text;
    }
    

    无论 InvokeRequired 的值如何,第一种方法始终使用 Invoke。第二种方法实际上可以做到这一点。当您总是从另一个线程调用函数时,此模式可用。

    【讨论】:

    • 这是一个肮脏的解决方案... :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-19
    • 1970-01-01
    • 1970-01-01
    • 2021-04-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多