多线程时如何避免可怕的Application.DoEvents()

时间:2014-01-09 23:39:24

标签: c# multithreading backgroundworker doevents

所以我一直在阅读很多文章,这些文章一直在阻止使用Application.DoEvents(),甚至说它永远不会被使用,但我似乎无法为我的场景找到一个好的选择。 。 我正在处理的应用程序有一个方法,当主GUI表单首次启动时,由this.Shown事件调用该方法。该方法执行一些花费大约一分钟时间的工作,因此相同的方法也创建了一个基本上是自定义进度条的表单。请记住,此进程当前是单线程的,因此当此方法正在运行时,主GUI和进度条将变为无响应。如果用户在此期间点击任意位置,屏幕将变为空白。所以我正在努力将这个方法的一些工作放在BackgroundWorker线程中。以下是我的想法:

private BackgroundWorker Bgw = new BackgroundWorker();
private int LoadPercentage = 0;

    //this sub is executed on the main UI thread
    public void RunBgw()
    {
        bool StopThread = false;
        //this object should be created in this method and needs to be updated as the child thread is doing work
        MyCustomDialog dialog = new MyCustomDialog();  

        dialog.UpdateProgress(0, "My message");

        dialog.Show();
        this.Invalidate();
        this.Refresh();

        //critical properties to set if you want to report progress/be able to cancel the operation
        Bgw.WorkerSupportsCancellation = true;
        Bgw.WorkerReportsProgress = true;

        //add handlers to Bgw so events will fire
        Bgw.DoWork += new DoWorkEventHandler(Bgw_DoWork);
        Bgw.ProgressChanged += new ProgressChangedEventHandler(Bgw_ProgressChanged);
        Bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Bgw_RunWorkerCompleted);

        //fire off thread
        Bgw.RunWorkerAsync();

        while (Bgw.IsBusy == true) 
        {
            if (BW.CancellationPending == true) 
            {
                StopThread = true;
                break;
            }
            Application.DoEvents();

            if(LoadPercentage == 10) 
            {
                dialog.UpdateProgress(LoadPercentage, "Still working...");
                this.Invalidate();
                this.Refresh();
            }
            if(LoadPercentage == 50) 
            {
                dialog.UpdateProgress(LoadPercentage, "Halfway done...");
                this.Invalidate();
                this.Refresh();
            }
            // etc...

            //slow down loop so it doesnt take up all the CPU
            Thread.Sleep(200);
        }

        if(!StopThread) {
            //continue with something else.
        }
    }

    private void Bgw_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker BgwLocal = sender as BackgroundWorker;

        if ((BgwLocal.CancellationPending == true))
        {
            e.Cancel = true;
            break;
        }
        else
        {
            TimeConsumingWork();
            BgwLocal.ReportProgress(10); //report progress back to the main UI thread
            TimeConsumingWork();
            BgwLocal.ReportProgress(15, SomeGuiIcon); //pass back specific gui icon
            TimeConsumingWork();
            BgwLocal.ReportProgress(50); 

            // etc...  
        }
    }

    private void Bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        LoadPercentage = e.ProgressPercentage; //set current percentage of progress

        if(LoadPercentage == 15)
        {
            GuiIcon1 = (Icon)e.UserState; //set gui icon
        }
    }

    private void Bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if ((e.Cancelled == true))
        {
            //error handling
        }
        else if (!(e.Error == null))
        {
            //error handling
        }
        else
        {
            //success
        }
    }

除了错误处理已被证明是困难和混乱之外,一切都运行良好。在更新主线程中的现有对象时,是否有更好的线程工作方式?

感谢阅读。

1 个答案:

答案 0 :(得分:3)

您不应该使用以下代码阻止UI线程:

while (Bgw.IsBusy == true) { ... }

相反,允许RunBgw()返回给调用者。使用BackgroundWorker上的事件来了解它何时完成。具体来说

Bgw.ProgressChanged += new ProgressChangedEventHandler(Bgw_ProgressChanged);

通过致电

报告进展情况
Bgw_ProgressChanged

Bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Bgw_RunWorkerCompleted);

原因

Bgw_RunWorkerCompleted
在完成BackgroundWorker时调用

Bgw_ProgressChanged内更新进度条。

Windows UI是事件驱动的。您的代码没有使用事件来控制程序执行。