从另一个线程更新Control的属性(label.Text)

Roc*_*ngh 2 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);
Run Code Online (Sandbox Code Playgroud)

当我单击按钮时,执行上面的代码.我没有StatusLabel.Text = "Started";立刻看到.似乎它等待// long-running code然后它被执行.

我想要的是在单击按钮后立即在标签中看到"已启动",并且当长时间运行的任务完成时,我想在标签上看到"完成".

Gro*_*roo 6

发生这种情况有两个原因.

首先,您通过指定TaskScheduler.FromCurrentSynchronizationContext()参数告诉任务在GUI线程上运行.这意味着您的处理不是在后台线程上进行,而是在GUI线程上进行.其次,更改控件的属性只会使其失效,这意味着只有在GUI线程处理完其他作业后才会重新绘制它.

换句话说,您将值设置为"Started"(并且标签仅使其自身无效),然后立即将"后台"任务排队到GUI线程,使其保持忙于绘制控件.在此期间,您的表单将显示为"挂起",您甚至可能无法移动它.

在Windows窗体中执行后台作业的最简单方法是使用a BackgroundWorker.但是,如果你真的想使用a 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"));
});
Run Code Online (Sandbox Code Playgroud)

或者,您可以通过将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;
}
Run Code Online (Sandbox Code Playgroud)

然后只需从您希望的任何地方调用该方法:

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");
    });
}
Run Code Online (Sandbox Code Playgroud)