如果我在这样的任务中更新我的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);
}
}
答案 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
。