更新控件';s属性(label.Text)

本文关键字:label Text 属性 控件 更新 | 更新日期: 2023-09-27 18:09:38

在我的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,然后执行它。

我想要的是在单击按钮后立即在标签中看到"已开始",当长时间运行的任务完成时,我希望在标签上看到"完成"。

更新控件';s属性(label.Text)

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

首先,通过指定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");
    });
}

如果我理解的话,按钮点击发生在UI线程中,所以从那里将标签文本设置为"Started"没有问题。然后从另一个线程启动长时间运行的代码。在长时间运行的代码完成后,从另一个线程调用Invoke方法来更新UI元素:

Invoke((Action) (() => StatusLabel.Text = "Done"));
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更新,则必须遵循与此方法相同的方式。因为这些更新在这个异步方法调用的同步方法中。所有连接的同步方法都应该像异步方法一样被处理;等待";。因为它们是从Task调用的。运行此方法,以便将它们转换为异步方法