astralmaster
astralmaster

Reputation: 2465

Background Worker's ReportProgress for an Asynchronous task

I am using a BackgroundWorker to send asynchronous HTTP requests (with RestSharp by the way) and need to pass the returned data (HTTP response) to the main thread to update some GUI components based on that data. For this I use ReportProgress method like this:

static void bw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;
    Object[] args = (Object[])e.Argument;

    string strURL = args[0].ToString();
    string strParam = args[1].ToString();

    for (int i=0; i<5; i++) {

        var client = new RestClient(strURL);
        var request = new RestRequest(strURL, Method.POST);

        request.AddParameter("someparam", strParam);        

        client.ExecuteAsync(request, response =>
        {
            string strRet = response.Content;
            worker.ReportProgress(i, strArr);
        } 

    }

}

This code will raise an exception on ReporProgress saying that it is illegal to call this method when the Background Worker has already finished its work and it is absolutely correct because by the time I receive HTTP response the bw_DoWork will already have finished executing. So as you can see I am dealing with an asynchronous task in already asynchronous Background Worker thread. I know that ReportProgress is marshaled to execute on GUI thread and I don't see any other ways to pass data back to the main form. How can this be fixed/corrected?

Upvotes: 0

Views: 785

Answers (1)

Muraad Nofal
Muraad Nofal

Reputation: 802

You need some way to let your background worker sleep/wait until all ExecuteAsync´s are finished. You can do it like this.

void bw_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = sender as BackgroundWorker;

    Object[] args = (Object[])e.Argument;

    string strURL = args[0].ToString();
    string strParam = args[1].ToString();

    int finishedCounter = 0;

    // A reset event that is set from the last ExecuteAsync
    AutoResetEvent finishedEvent = new AutoResetEvent(false);
    for (int i=0; i<5; i++) {     

        var client = new RestClient(strURL);
        var request = new RestRequest(strURL, Method.POST);

        request.AddParameter("someparam", strParam); 

        client.ExecuteAsync(request, response =>
        {
            string strRet = response.Content;
            worker.ReportProgress(i, strArr);

            // Check if this is the last worker/step then
            // wake up the background worker to finish the do_work method
            if (Interlocked.Increment(ref finishedCounter) == 5) 
                finishedEvent.Set();    
        });
    }

    // wait till work is done
    finishedEvent.WaitOne();
}

An alternative would be to not use a BackgroundWorker at all. You can update the UI in WPF using the MainWindow´s dispatcher.

System.Windows.Application.Current.Dispatcher.Invoke(() =>
    {
        // Update UI
    });

or 

System.Windows.Application.Current.MainWindow.Dispatcher.Invoke(() =>
    {
        // Update UI
    });

Or in Windows Forms using a concrete Form

System.Windows.Forms.Form form = ...;

form.Invoke(new Action( () =>
    {
        //Update UI
    }));

Upvotes: 1

Related Questions