Потокобезопасная обработка форм между двумя формами (WinForms C #)

#c# #winforms

#c# #winforms

Вопрос:

У меня есть две формы: основная форма и та, которая появляется в виде модального диалогового окна. Из процесса, созданного в основной форме, я хочу динамически обновлять текст в модальном диалоговом окне. Вот что у меня есть:

В основной форме я делаю это:

 // 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; , и оно не вызывает команду.

Я что-то упускаю в этой настройке?

Комментарии:

1. Вы запускаете поток слишком рано. Дождитесь модального. Событие загрузки.

2. Как сказал Ханс Пассант, я не уверен, что поток. Start() можно безопасно использовать перед модальным. ShowDialog() . Что именно является исключением?

3. Какое именно исключение вы получаете. Вполне вероятно, что, как сказал Ганс, поток выполняет первый вызов SetWaitLabelText до того, как форма будет полностью сконструирована.

4. С тех пор, как я опубликовал это, я больше не получал исключения. Я продолжу пытаться и опубликую, что такое исключение, а также попробую решение Ганса.

Ответ №1:

Как указал @Hans Passant, вам следует дождаться модального.Событие загрузки. Одним из хороших вариантов является использование 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:

Я думаю, вам следует переписать код, чтобы разрешить поток.Start() не вызывается перед модальным.ShowDialog() .

В качестве обходного пути вы можете попробовать следующее:

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

void SetWaitLabelText2(string text) {
    lblWaitMessage.Text = text;
}
  

Первый метод всегда использует Invoke , независимо от значения InvokeRequired . Второй метод действительно делает это. Этот шаблон можно использовать, когда вы всегда вызываете функцию из другого потока.

Комментарии:

1. Это грязное решение … 🙂