在我的 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
然后执行。
我想要的是在点击按钮后立即在标签中看到“Started”,而当长时间运行的任务完成时,我希望在标签上看到“Done”。
最佳答案
发生这种情况的原因有两个。
首先,您通过将 TaskScheduler.FromCurrentSynchronizationContext()
指定为参数,告诉任务在 GUI 线程上运行。这意味着您的处理不是发生在后台线程上,而是发生在 GUI 线程上。其次是更改控件的属性只会使其无效,这意味着它只会在 GUI 线程处理完其他作业后才会重新绘制。
换句话说,您将值设置为"Started"
(并且标签只会使自身失效),然后立即将“后台”任务排队到 GUI 线程,使其忙于绘制控制。在此期间,您的表单将显示为“已挂起”,您可能甚至无法移动它。
在 Windows 窗体中执行后台作业的最简单方法是使用 BackgroundWorker
。但是,如果您真的想使用 Task
,则使用不接受同步上下文的简单任务工厂方法,然后确保调用来自该后台线程的所有 UI 交互一个 GUI 线程:
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");
});
}
关于c# - 从另一个线程更新控件的属性 (label.Text),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24349140/