首页 文章

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

提问于
浏览
-3

因为我知道由于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

    在您的代码示例后更新了答案

    首先,拥有多个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 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 ,我表明这名 Worker 将要举起 ProgressChanged 事件 . 关于 ProgressChanged 事件的好处是,它在您的UI线程中执行 .

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

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

    工作线程本身已重置 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();
    }
    

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

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

相关问题