在任务中更新UI控件

时间:2014-08-20 18:29:10

标签: c# winforms asynchronous async-await task

如果我在这样的任务中更新我的UI控件,它是否正确代码?
或者它是错的,我需要像Control.Invoke一样使用smth?

private async void button1_Click(object sender, EventArgs e)
{
    textBox1.Text = await Task<string>.Factory.StartNew(() =>
    {
        Foo();
        return "Completed";
    });
}

private void Foo()
{
    for (int i = 0; i < 100; i++)
    {
        textBox1.Text = i.ToString();
        Thread.Sleep(1000);
    }
}

3 个答案:

答案 0 :(得分:7)

BCL使用在IProgress类中实现的Progress接口来解决此特定方案,以提供丰富的异步进度报告。这在.NET 4.5或带有BCL Portability Nuget包的.NET 4中可用。许多BCL类接受IProgress参数进行进度报告。

Servy的答案解决了在异步操作之后如何更新UI的直接问题,但这迫使您在长时间运行的操作中混合UI代码。 IProgress允许您使用报告数据进行OnReport调用,而无需担心将调用编组到正确的线程,同步上下文,UI特定的调用等。

您的代码可以像这样简单:

private async void button1_Click(object sender, EventArgs e)
{
    var progress=new Progress<string>(msg=>textBox1.Text = msg);
    await Task<string>.Factory.StartNew(() => Foo(progress));
}

private void Foo(IProgress<string> progress)
{
    for (int i = 0; i < 100; i++)
    {
        progress.OnReport(i.ToString());
        Thread.Sleep(1000);
    }
    progress.OnReport("Finished");
}

或者您可以使用更复杂的进度类型,例如

class MyProgressData
{
    public string Message{get;set;}
    public int Iteration {get;set;}
    public MyProgressData(string message,int iteration) ...
}

private async void button1_Click(object sender, EventArgs e)
{
    var progress=new Progress<MyProgressData>(msg=>{
        textBox1.Text = msg.Message;
        textBox2.Text=msg.Iteration.ToString();
    });
    await Task<string>.Factory.StartNew(() => Foo(progress));
}

private void Foo(IProgress<MyProgressData> progress)
{
    for (int i = 0; i < 100; i++)
    {
        progress.OnReport(new MyProgressData("Hi",i));
        Thread.Sleep(1000);
    }
    progress.OnReport("Finished");
}

这样做的好处在于您可以将处理与报告完全分离。您可以将处理代码放在完全不同的类中,甚至可以从UI中进行投影。

答案 1 :(得分:4)

您可以运行代码以查看它不起作用,并且当您从非UI线程触摸UI时,程序将崩溃。

您应该将UI 更新为计划在UI线程中运行的任务的延续。 await为您完成所有这些工作,使代码变得非常简单。

你也不应该创建一个线程池线程,只是让它坐在那里,在你睡了一段固定的时间时什么都不做。请改用Task.Delay

private async void button1_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 100; i++)
    {
        textBox1.Text = i.ToString();
        await Task.Delay(1000);
    }
    textBox1.Text = "Completed";
}

答案 2 :(得分:1)

您的Foo方法将抛出,因为您正在尝试从后台线程更新UI控件。 如果Foo做了一些随意的事情,而且没有任何UI控件,那么您的textBox1.Text = await Task.Factory.StartNew就可以了,因为您使用await并隐式捕获SynchronizationContext await完成后使用,这将在UI线程上进行分配。

这没有多大意义,但是如果您想从Task内部更新控件,则必须使用Control.Invoke