c# - 从另一个线程更新控件的属性 (label.Text)

标签 c# multithreading winforms

在我的 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/

相关文章:

c# - 将 c# winforms 按钮处理为击键?

winforms - WinForm 使用带有 SSO 的 webview2

.net - 哈希表并发

ios - 意外的核心数据多线程违规

java - 如何使用 try 和资源将 while 循环放入线程中?

.net - 强调SplitContainer

c# - 在 Wpf 中获取祖先源

c# - Xamarin - 在 C# 中设置 Azure 离线同步

c# - 使用 groupby、sum 和 count 将 SQL 转换为 Linq

javascript - 从代码隐藏调用 js 函数(不是 startupScript)