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

时间:2015-12-04 17:02:29

标签: c# multithreading thread-safety backgroundworker ui-thread

因为我知道由于UI和Worker线程之间的交互,无法将winform加载到后台工作者的DoWork中, 但我发现了一种方法,我定义了另一个显示Form的线程,然后将该线程启动到后台工作者的DoWork中。 它起作用了,我现在可以控制那个背景工作者的过程...但我不确定它是否是一种安全的方法。我的简化计划是:         命名空间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();         }

1 个答案:

答案 0 :(得分:0)

代码示例后更新的答案

首先,拥有多个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的代码。它使用EventHandler可以订阅的Form1(如下所示),以便在点击Form2.button1时获得通知。

Form1使用`BackgroundWorker`及其事件

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事件的处理程序。

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

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

作为奖励,我在第一个表单中单击时禁用button1,并在后台工作程序完成时重新启用它,以便用户在第一个表单运行时无法启动另一个线程。如果您希望多个线程并行运行,则代码会变得复杂一些。但由于只有一个进度条,我假设一次只应该有一个线程。

我希望这个解释可以帮到你。我重复一遍,阅读official documentation at MSDN

历史完整性的原始帖子

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

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

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

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

编辑:只是为了确定:在主UI线程中使用Form.Show(),而不是在后台工作程序中,而不是在另一个线程中。 Form.Show()立即返回,所以如果你在其中一个线程中调用它,那么这个线程将在之后终止,并且可能也会关闭窗口。