Question

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.

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top