从另一个线程更新标签的最简单方法是什么?

我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。

我怎么能这样做?


当前回答

只需使用以下内容:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });

其他回答

由于场景的平凡性,我实际上会让UI线程轮询状态。我想你会发现它可以很优雅。

public class MyForm : Form
{
  private volatile string m_Text = "";
  private System.Timers.Timer m_Timer;

  private MyForm()
  {
    m_Timer = new System.Timers.Timer();
    m_Timer.SynchronizingObject = this;
    m_Timer.Interval = 1000;
    m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
    m_Timer.Start();
    var thread = new Thread(WorkerThread);
    thread.Start();
  }

  private void WorkerThread()
  {
    while (...)
    {
      // Periodically publish progress information.
      m_Text = "Still working...";
    }
  }
}

该方法避免了使用ISynchronizeInvoke.Invoke和ISynchroniizeInvoke.BeginInvoke方法时所需的封送处理操作。使用封送处理技术没有什么问题,但有几点需要注意。

请确保不要过于频繁地调用BeginInvoke,否则它可能会超出消息泵。在工作线程上调用Invoke是一个阻塞调用。它将暂时停止该线程中正在执行的工作。

我在这个答案中提出的策略颠倒了线程的通信角色。UI线程轮询数据,而不是工作线程推送数据。这是许多场景中使用的常见模式。由于您所要做的只是显示工作线程的进度信息,所以我认为您会发现此解决方案是编组解决方案的一个很好的替代方案。它具有以下优点。

UI和工作线程保持松散耦合,而不是Control.Invoke或Control.BeginVoke方法紧密耦合它们。UI线程不会阻碍工作线程的进度。工作线程不能支配UI线程更新的时间。UI和工作线程执行操作的间隔可以保持独立。工作线程不能超出UI线程的消息泵。UI线程可以决定何时以及多久更新一次UI。

我的版本是插入一行递归的“咒语”:

对于无参数:

    void Aaaaaaa()
    {
        if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra

        // Your code!
    }

对于具有参数的函数:

    void Bbb(int x, string text)
    {
        if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; }
        // Your code!
    }

就是这样。


一些论证:通常在if()语句后一行放置{}对代码可读性不利。但在这种情况下,这是例行的“咒语”。如果此方法在项目中保持一致,则不会破坏代码的可读性。它还可以避免代码乱丢(一行代码而不是五行代码)。

当您看到(InvokeRequired){some long}时,您只知道“从另一个线程调用此函数是安全的”。

关于这个主题的另一个示例:我创建了一个抽象类UiSynchronizeModel,它包含一个公共方法实现:

public abstract class UiSynchronizeModel
{
    private readonly TaskScheduler uiSyncContext;
    private readonly SynchronizationContext winformsOrDefaultContext;

    protected UiSynchronizeModel()
    {
        this.winformsOrDefaultContext = SynchronizationContext.Current ?? new SynchronizationContext();
        this.uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext();
    }

    protected void RunOnGuiThread(Action action)
    {
        this.winformsOrDefaultContext.Post(o => action(), null);
    }

    protected void CompleteTask(Task task, TaskContinuationOptions options, Action<Task> action)
    {
        task.ContinueWith(delegate
        {
            action(task);
            task.Dispose();
        }, CancellationToken.None, options, this.uiSyncContext);
    }
}

模型或控制器类应从此抽象类派生。您可以使用任何模式(任务或手动管理的后台线程)并使用以下方法:

public void MethodThatCalledFromBackroundThread()
{
   this.RunOnGuiThread(() => {
       // Do something over UI controls
   });
}

任务示例:

var task = Task.Factory.StartNew(delegate
{
    // Background code
    this.RunOnGuiThread(() => {
        // Do something over UI controls
    });
});

this.CompleteTask(task, TaskContinuationOptions.OnlyOnRanToCompletion, delegate
{
    // Code that can safely use UI controls
});

我更喜欢这个:

private void UpdateNowProcessing(string nowProcessing)
{
    if (this.InvokeRequired)
    {
        Action<string> d = UpdateNowProcessing;
        Invoke(d, nowProcessing);
    }
    else
    {
        this.progressDialog.Next(nowProcessing);
    }            
}

在我的案例(WPF)中,解决方案简单如下:

private void updateUI()
{
    if (!Dispatcher.CheckAccess())
    {
        Dispatcher.BeginInvoke(updateUI);
        return;
    }

    // Update any number of controls here
}