简体   繁体   中英

Update progress bar from async method

I am trying to understand better how can I update a windows forms progress bar from an async operation but I am getting some unexpected behavior from that.

Basically I am having a button which should after is being clicked to update a progress bar and then set it back to 0 once the progress bar gets 100% updated.

This is my code:

    private async void button1_Click(object sender, EventArgs e)
    {
        await CallMethodAsync().ContinueWith((prevTask) => 
        {
            prevTask.Wait();
            progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; }));
        });
    }

    private static async Task ExecuteMethodAsync(IProgress<double> progress = null)
    {
        double percentComplete = 0;
        bool done = false;

        while (!done)
        {
            if (progress != null)
            {
                progress.Report(percentComplete);
            }

            percentComplete += 10;

            if(percentComplete == 100)
            {
                done = true;
            }
        }
    }

    private async Task CallMethodAsync()
    {
        var progress = new Progress<double>();
        progress.ProgressChanged += (sender, args) => { progressBar1.Increment(10); };
        await ExecuteMethodAsync(progress);
    }

Having this implementation the progress bar is not being updated at all even if I call "Wait()" on the operation that should update the value of the progress bar.

If i remove this part of code:

progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; }));

the progress bar gets updated but it remains all the time like that, and I want to set it back to 0 once it was entirely filled so that I can update it again when I click again the button.

Could someone please explain me what am I doing wrong ?

One of the reasons async-await syntax was invented because it was difficult to follow the sequence of instructions when tasks were concatenated using functions like ContinueWith .

If you use async-await it is seldom necessary to use statements like ContinueWith . After an await , the thread already continues with the statements after the await.

If the button is clicked, you want to call ExcecuteMethodAsync . This function takes an IProgress, because it wants to report progress regularly. You want to call this function asynchronously, so whenever the function has to wait for something, it doesn't really wait, but returns control to you so you could do other things instead of really waiting, until you encounter an await, in which case your caller continues processing until he encounters an await, etc.

The nice thing with async-await is that the thread that continues after your call to an async function has the same context as the calling thread. This means that you can regard it as your original thread. No InvokeRequired, no need to protect data with mutexes etc.

Your function could be simplified as follows:

async Task CallMethodAsync()
{
    var progress = new Progress<double>();
    progress.ProgressChanged += OnProgressReported;

    await ExecuteMethodAsync(progress);
}

private void OnProgressReported(object sender, ...)
{
    // because this thread has the context of the main thread no InvokeRequired!
    this.progressBar1.Increment(...);
}

private async void button1_Click(object sender, EventArgs e)
{
    await CallMethodAsync();
}

So when the button is clicked, CallMethodAsync is called. This function will create A Progress object and subscribes on its Report event. Note that this is still your UI-thread. Then it calls ExecuteMethodAsync, which will regularly raise event Report, which is handled by OnProgressReported.

Because ExecuteMethodAsync is async, you can be sure there is somewhere an await in it. This means that whenever it has to await, control returns to the caller, which is CallMethodAsync , until is encounters an await, which in this case is immediately.

Control goes up the call stack to the caller, which is button1_click, where it immediately encounters an await, so control goes up the call stack, etc.

All these controls have the same context: it is as if they are the same thread.

An article that helped me a lot to understand async-await is this interview with Eric Lippert. Search somewhere in the middle for async await

Another articel that helped me a lot to learn good practices were this article by the ever so helpful Stephen Cleary and Async/Await - Best Practices in Asynchronous Programming also by Stephen Cleary

Your issue is happening because ExecuteMethodAsync(...) is not actually asynchronous.
Add the following before the while loop to make it asynchronous

await Task.Delay(1);

or enclose some synchronous portion of code (eg the while loop) into a:

await Task.Run(() => { ... });

or (the best one), add the following at the beginning of the function:

await Task.Yield(); // Make us async right away

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM