本文介绍了从异步方法更新进度栏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图更好地理解如何从异步操作中更新Windows窗体进度栏,但由此引起了一些意外行为.

基本上我有一个按钮,单击该按钮后应更新进度条,然后在进度条获得100%更新后将其设置回0.

这是我的代码:

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

有了此实现,即使我在应该更新进度条值的操作上调用"Wait()",进度条也根本不会被更新.

如果我删除了这部分代码:

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

进度条得到更新,但是一直保持这样的状态,一旦完全填满,我想将其重新设置为0,这样当我再次单击按钮时就可以再次对其进行更新.

有人可以解释一下我在做什么错吗?

解决方案

发明了async-await语法的原因之一是因为使用诸如ContinueWith之类的函数连接任务时,很难遵循指令序列. >

如果使用async-await,则几乎不需要使用类似ContinueWith的语句. await之后,线程已经在等待之后继续执行语句.

如果单击该按钮,则要呼叫ExcecuteMethodAsync.该函数需要一个IProgress,因为它想定期报告进度.您想异步调用此函数,因此无论何时该函数必须等待某事,它实际上并不会等待,而是将控制权返回给您,因此您可以做其他事情而不是真正地等待,直到遇到等待时为止.您的呼叫者会继续处理,直到遇到等待等.

使用async-await的好处是,在调用异步函数之后继续执行的线程与调用线程具有相同的上下文.这意味着您可以将其视为原始线程.无需InvokeRequired,无需使用互斥锁等保护数据.

您的功能可以简化如下:

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();
}

因此,当单击按钮时,将调用CallMethodAsync.此函数将创建一个Progress对象并订阅其Report事件.请注意,这仍然是您的UI线程.然后,它调用ExecuteMethodAsync,它将定期引发事件报告,该事件报告由OnProgressReported处理.

由于ExecuteMethodAsync是异步的,因此可以确保其中有一个等待的地方.这意味着,每当必须等待时,控制权就会返回给调用方CallMethodAsync,直到它遇到等待(在这种情况下为立即).

控制权将调用堆栈上的呼叫堆栈(即button1_click)上移,立即遇到等待,因此控制权将呼叫堆栈上层,等等.

所有这些控件都具有相同的上下文:就像它们是同一线程一样.

这次对埃里克·利珀特(Eric Lippert)的采访.在中间某处搜索异步等待

这篇文章对我有很多帮助,可以学习良好做法由史蒂芬·克雷里(Stephen Cleary)如此有帮助的人异步/等待-最佳实践异步编程,同样由Stephen Cleary

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

这篇关于从异步方法更新进度栏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-01 19:10