How do I avoid the dreaded Application.DoEvents() when multithreading

906 Views Asked by At

So I've been reading a lot of articles that have been discouraging the use of Application.DoEvents() and even saying that it should never be used but I can't seem to find a good alternative for my scenario... The application that I am working on has a method that is called by a this.Shown event when the main GUI form first launches. The method does some work that takes about a minute of time so the same method also creates a form that is essentially a custom made progress bar. Keep in mind that this process is currently single threaded so when this method is doing work the main GUI and and progress bar become non-responsive. If the user clicks anywhere during this time, the screens go blank. So I'm working on putting some of the work that this method does in a BackgroundWorker thread. Here is what I've come up with:

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
        }
    }

Everything is working well except that error handling has proven to be difficult and messy. Is there a better way of threading work while updating an existing object in the main thread??

Thanks for reading.

1

There are 1 best solutions below

1
On BEST ANSWER

You should not be blocking the UI thread with this code:

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

Instead, allow RunBgw() to return to the caller. Use the events present on the BackgroundWorker to know when it has completed. Specifically

Bgw.ProgressChanged += new ProgressChangedEventHandler(Bgw_ProgressChanged);

reports on progress by calling

Bgw_ProgressChanged

and

Bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Bgw_RunWorkerCompleted);

causes

Bgw_RunWorkerCompleted

to be invoked when the BackgroundWorker is done.

Update the progress bar from within Bgw_ProgressChanged.

Windows UI's are event driven. Your code is not making use of events to control program execution.