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

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

我怎么能这样做?


当前回答

将一些公共变量放在一个单独的类中以保存值。

例子:

public  class data_holder_for_controls
{
    // It will hold the value for your label
    public string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();

    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread
    }

    public static void perform_logic()
    {
        // Put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            // Statements here
        }
        // Set the result in the status variable
        d1.status = "Task done";
    }
}

其他回答

我认为最简单的方法是:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }

对于.NET 2.0,下面是我编写的一段代码,它完全符合您的需要,适用于控件上的任何属性:

private delegate void SetControlPropertyThreadSafeDelegate(
    Control control, 
    string propertyName, 
    object propertyValue);

public static void SetControlPropertyThreadSafe(
    Control control, 
    string propertyName, 
    object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate               
    (SetControlPropertyThreadSafe), 
    new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(
        propertyName, 
        BindingFlags.SetProperty, 
        null, 
        control, 
        new object[] { propertyValue });
  }
}

这样称呼:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

如果您使用的是.NET 3.0或更高版本,可以将上述方法重写为Control类的扩展方法,这样可以简化对以下内容的调用:

myLabel.SetPropertyThreadSafe("Text", status);

2010年10月5日更新:

对于.NET 3.0,应使用以下代码:

private delegate void SetPropertyThreadSafeDelegate<TResult>(
    Control @this, 
    Expression<Func<TResult>> property, 
    TResult value);

public static void SetPropertyThreadSafe<TResult>(
    this Control @this, 
    Expression<Func<TResult>> property, 
    TResult value)
{
  var propertyInfo = (property.Body as MemberExpression).Member 
      as PropertyInfo;

  if (propertyInfo == null ||
      !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
      @this.GetType().GetProperty(
          propertyInfo.Name, 
          propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
  }

  if (@this.InvokeRequired)
  {
      @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> 
      (SetPropertyThreadSafe), 
      new object[] { @this, property, value });
  }
  else
  {
      @this.GetType().InvokeMember(
          propertyInfo.Name, 
          BindingFlags.SetProperty, 
          null, 
          @this, 
          new object[] { value });
  }
}

它使用LINQ和lambda表达式来允许更干净、更简单和更安全的语法:

// status has to be of type string or this will fail to compile
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status);

现在不仅在编译时检查了属性名称,而且属性的类型也是如此,因此不可能(例如)将字符串值分配给布尔属性,从而导致运行时异常。

不幸的是,这并不能阻止任何人做一些愚蠢的事情,例如传递另一个控件的属性和值,因此以下内容将很好地编译:

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

因此,我添加了运行时检查,以确保传入的属性实际上属于调用该方法的控件。虽然不完美,但仍比.NET2.0版本好得多。

如果有人对如何改进此代码以确保编译时安全有任何进一步的建议,请发表评论!

当您在UI线程中时,可以向它请求其同步上下文任务调度器。它将为您提供一个TaskScheduler,用于调度UI线程上的所有内容。

然后,您可以将任务链接起来,这样当结果准备就绪时,另一个任务(在UI线程上安排)会选择它并将其分配给标签。

public partial class MyForm : Form
{
  private readonly TaskScheduler _uiTaskScheduler;
  public MyForm()
  {
    InitializeComponent();
    _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  }

  private void buttonRunAsyncOperation_Click(object sender, EventArgs e)
  {
    RunAsyncOperation();
  }

  private void RunAsyncOperation()
  {
    var task = new Task<string>(LengthyComputation);
    task.ContinueWith(antecedent =>
                         UpdateResultLabel(antecedent.Result), _uiTaskScheduler);
    task.Start();
  }

  private string LengthyComputation()
  {
    Thread.Sleep(3000);
    return "47";
  }

  private void UpdateResultLabel(string text)
  {
    labelResult.Text = text;
  }
}

这适用于任务(而不是线程),这是目前编写并发代码的首选方式。

这是您应该采用的经典方法:

using System;
using System.Windows.Forms;
using System.Threading;

namespace Test
{
    public partial class UIThread : Form
    {
        Worker worker;

        Thread workerThread;

        public UIThread()
        {
            InitializeComponent();

            worker = new Worker();
            worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged);
            workerThread = new Thread(new ThreadStart(worker.StartWork));
            workerThread.Start();
        }

        private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e)
        {
            // Cross thread - so you don't get the cross-threading exception
            if (this.InvokeRequired)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    OnWorkerProgressChanged(sender, e);
                });
                return;
            }

            // Change control
            this.label1.Text = e.Progress;
        }
    }

    public class Worker
    {
        public event EventHandler<ProgressChangedArgs> ProgressChanged;

        protected void OnProgressChanged(ProgressChangedArgs e)
        {
            if(ProgressChanged!=null)
            {
                ProgressChanged(this,e);
            }
        }

        public void StartWork()
        {
            Thread.Sleep(100);
            OnProgressChanged(new ProgressChangedArgs("Progress Changed"));
            Thread.Sleep(100);
        }
    }


    public class ProgressChangedArgs : EventArgs
    {
        public string Progress {get;private set;}
        public ProgressChangedArgs(string progress)
        {
            Progress = progress;
        }
    }
}

工作线程有一个事件。UI线程启动另一个线程来完成工作,并挂接该工作线程事件,以便显示工作线程的状态。

然后在UI中,您需要跨线程来更改实际控件。。。如标签或进度条。

当我遇到同样的问题时,我向谷歌寻求帮助,但它并没有给我一个简单的解决方案,而是举了MethodInvoker和blah blah的例子,让我更加困惑。所以我决定自己解决。以下是我的解决方案:

制作一个这样的代理:

Public delegate void LabelDelegate(string s);

void Updatelabel(string text)
{
   if (label.InvokeRequired)
   {
       LabelDelegate LDEL = new LabelDelegate(Updatelabel);
       label.Invoke(LDEL, text);
   }
   else
       label.Text = text
}

您可以像这样在新线程中调用此函数

Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();

不要与线程(()=>…..)混淆。在处理线程时,我使用匿名函数或lambda表达式。为了减少代码行,您也可以使用ThreadStart(..)方法,我不打算在这里解释。