在我的Windows应用程序中,我想在单击某个按钮时从另一个线程更新标签的Text
属性:
以下是我的按钮点击事件处理程序的代码:
StatusLabel.Text = "Started";
Task.Factory
.StartNew(() =>
{
… // long-running code
StatusLabel.Text = "Done";
}, CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext())
.ContinueWith(tsk =>
{
MessageBox.Show("something broke");
var flattened = tsk.Exception.Flatten();
// note: Don't actually handle exceptions this way, m'kay?
flattened.Handle(ex => { MessageBox.Show("Error:" + ex.Message); return true; });
}, TaskContinuationOptions.OnlyOnFaulted);
当我点击按钮时,执行上面的代码。我没有立刻看到StatusLabel.Text = "Started";
。它似乎等待// long-running code
然后执行。
我想要的是看到"开始"单击按钮后在标签中,当长时间运行的任务完成后,我想看到"完成"在标签上。
答案 0 :(得分:6)
发生这种情况有两个原因。
首先,通过将TaskScheduler.FromCurrentSynchronizationContext()
指定为参数,告诉您在GUI线程上运行任务。这意味着您的处理不是在后台线程上进行,而是在GUI线程上进行。其次,更改控件的属性只会使其失效,这意味着只有在GUI线程处理完其他作业后才会重新绘制它。
换句话说,您将值设置为"Started"
(并且标签仅使其自身无效),然后立即将“后台”任务排队到GUI线程,使其保持忙于绘制控件。在此期间,您的表单将显示为“挂起”,您甚至可能无法移动它。
在Windows窗体中执行后台作业的最简单方法是使用BackgroundWorker
。但是,如果您真的想使用Task
,那么使用不接受同步上下文的简单任务工厂方法,然后确保在GUI线程上调用该后台线程的所有UI交互:
StatusLabel.Text = "Started";
// this is the simple Task.Factory.StartNew(Action) overload
Task.Factory.StartNew(() =>
{
// do some lengthy processing
Thread.Sleep(1000);
// when done, invoke the update on a gui thread
StatusLabel.Invoke(new Action(() => StatusLabel.Text = "Done"));
});
或者,您可以通过将GUI线程同步逻辑移动到单独的方法来简化整个过程:
// this method can be invoked from any thread
private void UpdateStatusLabel(string msg)
{
if (StatusLabel.InvokeRequired)
{
StatusLabel.Invoke(new Action<string>(UpdateStatusLabel), msg);
return;
}
StatusLabel.Text = msg;
}
然后只需从任何地方调用该方法:
private void button1_Click(object sender, EventArgs e)
{
UpdateStatusLabel("Started");
Task.Factory.StartNew(() =>
{
// do some lengthy processing
Thread.Sleep(10000);
// no need to invoke here
UpdateStatusLabel("Done");
});
}
答案 1 :(得分:0)
如果我理解,按钮点击会在UI线程中发生,因此将标签文本设置为&#34; Started&#34;从那里。然后从不同的线程启动长时间运行的代码。在长时间运行的代码完成后,从另一个线程调用Invoke
方法来更新UI元素:
Invoke((Action) (() => StatusLabel.Text = "Done"));
答案 2 :(得分:0)
private async void Button_Clicked(object sender, EventArgs e)
{
Device.BeginInvokeOnMainThread(() =>
{
// UI updates ( label text change, button enable/disable )
});
await task;
await Task.Run(()=> {
// Synchronous methods
});
}
可以根据需求以任何方式安排这些内容。 全部连续运行。像这里一样,首先更新UI,然后完成任务,然后运行其他同步方法。
如果这些同步方法再次具有UI更新,则必须遵循与该方法相同的方法。由于这些更新都在此异步方法调用的同步方法内部。所有连接的同步方法都应仅作为异步方法对待,而无需“ await”。因为它们是从此方法的Task.Run调用的,所以它们被转换为异步方法