C# 如何从另一个线程更新GUI?

C# 如何从另一个线程更新GUI?,c#,.net,multithreading,winforms,user-interface,C#,.net,Multithreading,Winforms,User Interface,从另一个线程更新标签的最简单方法是什么 我有一个表单在thread1上运行,从中我开始了另一个线程(thread2) 当thread2正在处理一些文件时,我想用thread2的当前工作状态更新表单上的标签 我该怎么做呢?您必须确保更新发生在正确的线程上;用户界面线程 为了做到这一点,您必须调用事件处理程序,而不是直接调用它 您可以通过以下方式提高您的活动: // thread-safe equivalent of // myLabel.Text = status; SetControlPro

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

  • 我有一个
    表单
    thread1
    上运行,从中我开始了另一个线程(
    thread2

  • thread2
    正在处理一些文件时,我想用
    thread2
    的当前工作状态更新
    表单上的
    标签


我该怎么做呢?

您必须确保更新发生在正确的线程上;用户界面线程

为了做到这一点,您必须调用事件处理程序,而不是直接调用它

您可以通过以下方式提高您的活动:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);
myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
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
}
 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });
(这里输入的代码是我脑子里想出来的,所以我没有检查语法是否正确,等等,但它应该能让你继续使用。)

请注意,上面的代码在WPF项目上不起作用,因为WPF控件不实现
ISynchronizeInvoke
接口

为了确保上述代码适用于Windows窗体和WPF以及所有其他平台,您可以查看
AsyncOperation
AsyncOperationManager
SynchronizationContext

为了以这种方式轻松引发事件,我创建了一个扩展方法,它允许我通过调用以下命令简化引发事件:

MyEvent.Raise(this, EventArgs.Empty);

当然,您也可以使用BackGroundWorker类,它将为您抽象这个问题。

简单的解决方案是使用
控件。调用

void DoSomething()
{
    if (InvokeRequired) {
        Invoke(new MethodInvoker(updateGUI));
    } else {
        // Do Something
        updateGUI();
    }
}

void updateGUI() {
    // update gui here
}

您需要在GUI线程上调用该方法。您可以通过调用Control.Invoke来实现这一点

例如:

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), message);
         return;
    }

    MyLabelControl.Text = message;
}

最简单的方法是将匿名方法传递到:


请注意,
Invoke
会阻止执行,直到它完成为止——这是同步代码。这个问题不涉及异步代码,但当您想了解异步代码时,有很多关于编写异步代码的问题。

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

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;
        }
    }
}
使用系统;
使用System.Windows.Forms;
使用系统线程;
名称空间测试
{
公共部分类UIThread:表单
{
工人;
螺纹加工螺纹;
公共UIThread()
{
初始化组件();
工人=新工人();
worker.ProgressChanged+=新事件处理程序(OnWorkerProgressChanged);
workerThread=新线程(新线程开始(worker.StartWork));
workerThread.Start();
}
WorkerProgressChanged上的私有void(对象发送方,ProgressChangedArgs e)
{
//交叉线程-因此不会出现交叉线程异常
if(this.invokererequired)
{
此.BeginInvoke((MethodInvoker)委托
{
OnWorkerProgressChanged(发送方,e);
});
返回;
}
//变更控制
this.label1.Text=e.进度;
}
}
公社工人
{
公共事件处理程序已更改;
ProgressChanged上的受保护无效(ProgressChangedArgs e)
{
if(ProgressChanged!=null)
{
(本,e);
}
}
公共网络
{
睡眠(100);
OnProgressChanged(新的ProgressChangedArgs(“进度变更”));
睡眠(100);
}
}
公共类ProgressChangedArgs:EventArgs
{
公共字符串进度{get;private set;}
公共进度变更码(字符串进度)
{
进步=进步;
}
}
}
您的工作线程有一个事件。您的UI线程启动另一个线程来执行工作,并连接该工作线程事件,以便您可以显示工作线程的状态


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

对于.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);
myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
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
}
 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });
如果您使用的是.NET 3.0或更高版本,则可以将上述方法重写为
控件
类的扩展方法,从而简化对以下对象的调用:

myLabel.SetPropertyThreadSafe("Text", status);
2010年10月5日更新:

#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
    var _with1 = diagwindow;
    if (_with1.InvokeRequired) {
        _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
    } else {
        UpdateDiag(whatmessage);
    }
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
    var _with2 = diagwindow;
    _with2.appendtext(whatmessage);
    _with2.SelectionStart = _with2.Text.Length;
    _with2.ScrollToCaret();
}
#endregion
对于.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 });
  }
}
现在不仅在编译时检查属性名称,属性的类型也是如此,因此(例如)不可能将字符串值分配给布尔属性,从而导致运行时异常

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

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
因此,我添加了运行时检查,以确保传入的属性实际上属于调用该方法的
控件。虽然不完美,但仍比.NET2.0版本好很多


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

出于许多目的,它就这么简单:

public delegate void serviceGUIDelegate();
private void updateGUI()
{
  this.Invoke(new serviceGUIDelegate(serviceGUI));
}

“serviceGUI()”是表单(this)中的GUI级方法,可以根据需要更改任意多个控件。从另一个线程调用“updateGUI()”。可以将参数添加到传递值中,或者(可能更快)根据需要使用带有锁的类作用域变量(如果访问它们的线程之间存在可能导致不稳定的冲突)。如果非GUI线程是时间关键型的,请使用BeginInvoke而不是Invoke(记住Brian Gideon的警告)

由于场景的琐碎性,我实际上会让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...";
    }
  }
}
该方法避免了使用时所需的封送处理操作
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");
public static void SetPropertyInGuiThread<C,V>(this C control, Expression<Func<C, V>> property, V value) where C : Control
{
    var memberExpression = property.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    if (control.InvokeRequired)
        control.Invoke(
            (Action<C, Expression<Func<C, V>>, V>)SetPropertyInGuiThread,
            new object[] { control, property, value }
        );
    else
        propertyInfo.SetValue(control, value, null);
}
myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  form_Diagnostics.updateDiagWindow(whatmessage);
#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
    var _with1 = diagwindow;
    if (_with1.InvokeRequired) {
        _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
    } else {
        UpdateDiag(whatmessage);
    }
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
    var _with2 = diagwindow;
    _with2.appendtext(whatmessage);
    _with2.SelectionStart = _with2.Text.Length;
    _with2.ScrollToCaret();
}
#endregion
    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!
    }
Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
control.Invoke(new Action(() => control.Text = "new text"));
private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}
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();
// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();

...

// In some non-UI Thread

// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);

...

void UpdateGUI(object userData)
{
    // Update your GUI controls here
}
public static class ExtensionMethods
{
    private static Action EmptyDelegate = delegate() { };

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}
private async void Button_Clicked(object sender, EventArgs e)
{
    var progress = new Progress<string>(s => label.Text = s);
    await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress),
                                TaskCreationOptions.LongRunning);
    label.Text = "completed";
}
class SecondThreadConcern
{
    public static void LongWork(IProgress<string> progress)
    {
        // Perform a long running work...
        for (var i = 0; i < 10; i++)
        {
            Task.Delay(500).Wait();
            progress.Report(i.ToString());
        }
    }
}
private async void Button_Click(object sender, EventArgs e)
{
    button.Enabled = false;

    try
    {
        var progress = new Progress<string>(s => button.Text = s);
        await Task.Run(() => SecondThreadConcern.FailingWork(progress));
        button.Text = "Completed";
    }
    catch(Exception exception)
    {
        button.Text = "Failed: " + exception.Message;
    }

    button.Enabled = true;
}

class SecondThreadConcern
{
    public static void FailingWork(IProgress<string> progress)
    {
        progress.Report("I will fail in...");
        Task.Delay(500).Wait();

        for (var i = 0; i < 3; i++)
        {
            progress.Report((3 - i).ToString());
            Task.Delay(500).Wait();
        }

        throw new Exception("Oops...");
    }
}
private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
SynchronizationContext _context;
var _context = SynchronizationContext.Current;
_context.Send(status =>{
    // UPDATE LABEL
}, null);
Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
    lblThreshold.Text = Speed_Threshold.ToString();
}));
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;
  }
}
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate { 
    someLabel.Text = newText; // runs on UI thread
});
public partial class MyForm : Form
{
    private readonly SynchronizationContext _context;
    public MyForm()
    {
        _context = SynchronizationContext.Current
        ...
    }

    private MethodOnOtherThread()
    {
         ...
         _context.Post(status => someLabel.Text = newText,null);
    }
}
 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });
this.Dispatcher.Invoke((Action)(() =>
{
    // This refers to a form in a WPF application 
    val1 = textBox.Text; // Access the UI 
}));
BackgroundWorker workerAllocator;
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1) {
    // This is my DoWork function.
    // It is given as an anonymous function, instead of a separate DoWork function

    // I need to update a message to textbox (txtLog) from this thread function

    // Want to write below line, to update UI
    txt.Text = "my message"

    // But it fails with:
    //  'System.InvalidOperationException':
    //  "The calling thread cannot access this object because a different thread owns it"
}
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
    // The below single line works
    txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}
txtLog.Invoke((MethodInvoker)delegate
{
    txtLog.Text = "my message";
});
public static void InvokeIfRequired<T>(this T c, Action<T> action) where T : Control
{
    if (c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}
object1.InvokeIfRequired(c => { c.Visible = true; });
object1.InvokeIfRequired(c => { c.Text = "ABC"; });
object1.InvokeIfRequired(c => 
  { 
      c.Text = "ABC";
      c.Visible = true; 
  }
);