将UI线程运行到后台工作线程中

本文关键字:线程 工作 后台 运行 UI | 更新日期: 2023-09-27 18:25:37

据我所知,由于UI和Worker线程之间的交互,不可能将winform加载到后台工作程序的DoWork中,但我找到了一种方法,我定义了另一个显示Form的线程,然后将该线程启动到后台工作人员的DoWork中。它起了作用,我现在可以控制那个幕后工作人员的过程了。。。但我不确定这是否是一种安全的方法。我的简化计划是:

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        void UI1()
        {
            using (Form2 f = new Form2())
                f.ShowDialog();
        }
        Thread ui1;
        private void Form1_Load(object sender, EventArgs e)
        { 
         ui1 = new Thread(UI1);
         Control.CheckForIllegalCrossThreadCalls = false;
         temp.backgroundWorker1.DoWork+=new DoWorkEventHandler(backgroundWorker1_DoWork);  
        }
        private void button1_Click(object sender, EventArgs e)
        {
        temp.backgroundWorker1.RunWorkerAsync();        
        }
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            progressBar1.Minimum = 0;
            progressBar1.Maximum = 100000;
            progressBar1.Value = 0;
            for (int i = 0; i < 100000; i++)
            {
               progressBar1.Value++;
               if (i == 5000)
               {
                   ui1.Start();
                   temp.backgroundWorker1.CancelAsync();
                   temp.ew.Reset();
               }
               if (temp.backgroundWorker1.CancellationPending)
                    temp.ew.WaitOne();
            }     
       }
    }

以及在Form2:中

        private void button1_Click(object sender, EventArgs e)
        {
            temp.ew.Set();
            this.Close();
        }

将UI线程运行到后台工作线程中

在代码示例后更新答案

首先,拥有多个UI线程或从不同的线程访问UI是一个非常糟糕的主意。这是许多错误的来源,这些错误将很难再现和调试。

因此,在生产代码中永远不应该使用设置Control.CheckForIllegalCrossThreadCalls = false

我将尝试给您一个代码示例,它做的事情(几乎)与您的相同,但没有任何第二个UI线程。

Form2和与Form1的通信

public partial class Form2 : Form
{
  public Form2()
  {
    InitializeComponent();
  }
  public event EventHandler ButtonClicked;
  protected virtual void OnButtonClicked()
  {
    EventHandler handler = ButtonClicked;
    if (handler != null) handler(this, EventArgs.Empty);
  }
  private void button1_Click(object sender, EventArgs e)
  {
    OnButtonClicked();
    Close();
  }
}

这是我为您的Form2编写的代码。它使用Form1可以订阅的EventHandler(如下所示)来获得点击Form2.button1时的通知。

使用"BackgroundWorker"及其事件的Form1

public partial class Form1 : Form
{
  private readonly BackgroundWorker backgroundWorker1 = new BackgroundWorker();
  private readonly ManualResetEvent ew = new ManualResetEvent(true);
  public Form1()
  {
    InitializeComponent();
    // this can all be done in designer too
    backgroundWorker1.WorkerReportsProgress = true;
    backgroundWorker1.DoWork += backgroundWorker1_DoWork;
    backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
    backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
    progressBar1.Minimum = 0;
    progressBar1.Maximum = 10000;
    progressBar1.Value = 0;
  }
  private void button1_Click(object sender, EventArgs e)
  {
    button1.Enabled = false;
    ew.Set();
    backgroundWorker1.RunWorkerAsync();
  }
  private void form2_ButtonClicked(object sender, EventArgs e)
  {
    ew.Set();
  }
  private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
  {
    for (int i = 0; i < 10000; i++)
    {
      if (i == 5000) ew.Reset();
      backgroundWorker1.ReportProgress(i);
      ew.WaitOne();
    }
  }
  private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
  {
    progressBar1.Value = e.ProgressPercentage;
    if (e.ProgressPercentage != 5000) return;
    Form2 form2 = new Form2();
    form2.ButtonClicked += form2_ButtonClicked;
    form2.Show();
  }
  private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  {
    button1.Enabled = true;
  }
}

因此,使用backgroundWorker1.ReportsProgress = true,我指示该工作者将引发ProgressChanged事件。ProgressChanged事件的好处是,它在UI线程中执行。

当报告进度为5000时,我创建Form2的实例,并将form2_ButtonClicked方法注册为该实例的ButtonClicked事件的处理程序。

然后我使用Form.Show而不是Form.ShowDialog打开新的form2,所以它不是模态的。

工作线程本身已经重置了ew,因此ew.WaitOne现在将阻塞,直到ew再次被设置。这就是在提到的事件处理程序form2_ButtonClicked中发生的情况。

作为奖励,我在单击第一个Form时禁用了button1,并在后台工作程序完成后重新启用它,这样用户就无法在第一个线程运行时启动另一个线程。如果希望多个线程并行运行,代码会变得稍微复杂一些。但由于只有一个进度条,我认为一次应该只有一个线程。

我希望这个解释对你有帮助。我重复我的愿望,阅读MSDN上的官方文档!


历史完整性的原始帖子

backgroundWorker1_DoWork方法实际上已经在与启动后台工作程序的UI线程不同的线程中运行。

您正在启动另一个显示对话框的线程。最后,你可以直接在后台工作人员中完成:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
  using (Form2 f = new Form2())
    f.ShowDialog();
}

这两种方法都很难回答您的问题:您的解决方案与直接从后台工作人员打开表单一样不安全!问题是,为什么要这样做?为什么您的新Form2应该在不同的线程中运行?如果您想要一个非模态窗口,请考虑使用Form.Show()而不是ShowDialog(这样您的用户就可以切换到父窗口,而无需关闭Form2)。

编辑:只是为了确保:在主UI线程中使用Form.Show(),而不是在后台工作线程中,也不是在其他线程中。Form.Show()会立即返回,因此如果您在其中一个线程中调用它,该线程将在之后终止,并可能关闭窗口。