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

从另一个Thread更新Label的最简单方法是什么?

  • 我有一个Form运行在thread1上,然后我开始另一个线程(thread2)。

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

我怎么能那么做?

795748 次浏览

您必须确保更新发生在正确的线程上;UI线程。

为此,您必须调用事件处理程序,而不是直接调用它。

你可以通过像这样提高你的事件来做到这一点:

(代码是在我的脑海中输入的,所以我没有检查正确的语法等,但它应该让你开始。)

if( MyEvent != null ){Delegate[] eventHandlers = MyEvent.GetInvocationList();
foreach( Delegate d in eventHandlers ){// Check whether the target of the delegate implements// ISynchronizeInvoke (Winforms controls do), and see// if a context-switch is required.ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;
if( target != null && target.InvokeRequired ){target.Invoke (d, ... );}else{d.DynamicInvoke ( ... );}}}

请注意,上面的代码不适用于WPF项目,因为WPF控件不实现ISynchronizeInvoke接口。

为了确保上面的代码适用于Windows窗体和WPF以及所有其他平台,您可以查看AsyncOperationAsyncOperationManagerSynchronizationContext类。

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

MyEvent.Raise(this, EventArgs.Empty);

当然,您也可以使用BackGroundWorker类,它将为您抽象这件事。

简单的解决方案是使用Control.Invoke

void DoSomething(){if (InvokeRequired) {Invoke(new MethodInvoker(updateGUI));} else {// Do SomethingupdateGUI();}}
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;}

最简单方法是传递给#0的匿名方法:

// Running on the worker threadstring newText = "abc";form.Label.Invoke((MethodInvoker)delegate {// Running on the UI threadform.Label.Text = newText;});// Back on the worker thread

请注意,Invoke会阻止执行直到它完成——这是同步代码。这个问题并不询问异步代码,但是当你想了解异步代码时,有很多关于编写异步代码的Stack Overflow上的内容

这是你应该做的经典方式:

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 exceptionif (this.InvokeRequired){this.BeginInvoke((MethodInvoker)delegate{OnWorkerProgressChanged(sender, e);});return;}
// Change controlthis.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中,您需要跨线程来更改实际控件……例如标签或进度条。

对于. NET 2.0,以下是我编写的一段很好的代码,它完全符合您的要求,并且适用于Control上的任何属性:

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);

更新05/10/2010:

对于. 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).Memberas 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 compilemyLabel.SetPropertyThreadSafe(() => myLabel.Text, status);

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

不幸的是,这并不能阻止任何人做愚蠢的事情,比如传入另一个Control的属性和值,所以下面的代码会很好地编译:

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

因此,我添加了运行时检查以确保传入的属性确实属于调用该方法的Control。不完美,但仍然比. NET 2.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...";}}}

该方法避免了使用ISynchronizeInvoke.InvokeISynchronizeInvoke.BeginInvoke方法时所需的封送处理操作。使用封送处理技术没有错,但您需要注意几个警告。

  • 确保不要过于频繁地调用BeginInvoke,否则它可能会溢出消息泵。
  • 在工作线程上调用Invoke是阻塞调用。它将暂时停止在该线程中完成的工作。

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

  • UI和工作线程保持松散耦合,而不是紧密耦合它们的Control.InvokeControl.BeginInvoke方法。
  • UI线程不会妨碍工作线程的进度。
  • 工作线程不能支配UI线程更新的时间。
  • UI和工作线程执行操作的间隔可以保持独立。
  • 工作线程不能溢出UI线程的消息泵。
  • UI线程可以决定UI更新的时间和频率。

线程代码通常有错误,并且总是难以测试。您不需要编写线程代码来从后台任务更新用户交互界面。只需使用背景人员类来运行任务及其报告进展方法来更新用户交互界面。通常,您只需报告完成百分比,但还有另一个包含状态对象的重载。这是一个仅报告字符串对象的示例:

    private void button1_Click(object sender, EventArgs e){backgroundWorker1.WorkerReportsProgress = true;backgroundWorker1.RunWorkerAsync();}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e){Thread.Sleep(5000);backgroundWorker1.ReportProgress(0, "A");Thread.Sleep(5000);backgroundWorker1.ReportProgress(0, "B");Thread.Sleep(5000);backgroundWorker1.ReportProgress(0, "C");}
private void backgroundWorker1_ProgressChanged(object sender,ProgressChangedEventArgs e){label1.Text = e.UserState.ToString();}

如果您总是想要更新相同的字段,这很好。如果您有更复杂的更新要做,您可以定义一个类来表示UI状态并将其传递给ReportDevelopment方法。

最后一件事,一定要设置WorkerReportsProgress标志,否则ReportProgress方法将被完全忽略。

火并忘记. NET 3.5+的扩展方法

using System;using System.Windows.Forms;
public static class ControlExtensions{/// <summary>/// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread./// </summary>/// <param name="control"></param>/// <param name="code"></param>public static void UIThread(this Control @this, Action code){if (@this.InvokeRequired){@this.BeginInvoke(code);}else{code.Invoke();}}}

这可以使用以下代码行调用:

this.UIThread(() => this.myLabel.Text = "Text Goes Here");

这个类似于上面使用的解决方案。NET Framework 3.0,但它解决了编译时安全支持的问题。

public  static class ControlExtension{delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);
public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value){if (source.InvokeRequired){var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);source.Invoke(del, new object[]{ source, selector, value});}else{var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;propInfo.SetValue(source, value, null);}}}

要使用:

this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);

如果用户传递错误的数据类型,编译器将失败。

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");

这是我的C#3.0版本的Ian Kemp的解决方案:

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 });elsepropertyInfo.SetValue(control, value, null);}

你这样称呼它:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. 它将空值检查添加到“作为成员表达式”的结果中。
  2. 它提高了静态类型安全性。

否则,原始是一个非常好的解决方案。

在搜索了这个问题之后,我发现FrankG俄勒冈幽灵的答案对我来说是最简单最有用的。现在,我在Visual Basic中编码并通过转换器运行这个片段;所以我不确定结果如何。

我有一个名为form_Diagnostics,的对话框表单,它有一个名为updateDiagWindow,的富文本框,我用它作为一种日志显示。我需要能够从所有线程更新其文本。额外的行允许窗口自动滚动到最新的行。

所以,我现在可以用一行来更新显示,从整个程序的任何地方,以你认为它可以在没有任何线程的情况下工作的方式:

  form_Diagnostics.updateDiagWindow(whatmessage);

Main代码(将其放在表单的类代码中):

#region "---------Update Diag Window Text------------------------------------"// This sub allows the diag window to be updated by all threadspublic 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 threadsprivate delegate void UpdateDiagDelegate(string whatmessage);private void UpdateDiag(string whatmessage){var _with2 = diagwindow;_with2.appendtext(whatmessage);_with2.SelectionStart = _with2.Text.Length;_with2.ScrollToCaret();}#endregion

我的版本是插入递归“mantra”的一条线

对于没有参数:

    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 ()语句后面一行不利于代码易读性。但在这种情况下,这是例行公事。如果这个方法在整个项目中是一致的,它不会破坏代码的易读性。它可以节省你的代码乱扔(一行代码而不是五行)。

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

Label lblText; //initialized elsewhere
void AssignLabel(string text){if (InvokeRequired){BeginInvoke((Action<string>)AssignLabel, text);return;}
lblText.Text = text;}

请注意,BeginInvoke()Invoke()更受欢迎,因为它不太可能导致死锁(但是,当仅将文本分配给标签时,这不是问题):

使用Invoke()时,你正在等待方法返回。现在,可能是你在调用的代码中做了一些需要等待线程的事情,如果它埋在你正在调用的一些函数中,这可能不会立即明显,这本身可能通过事件处理程序间接发生。所以你会等待线程,线程会等待你,而你是死锁的。

这实际上导致我们发布的一些软件挂起。通过将Invoke()替换为BeginInvoke()很容易修复。除非您需要同步操作,如果您需要返回值,可能会出现这种情况,否则请使用BeginInvoke()

. NET 4的Marc Gravell最简单的解决方案变体:

control.Invoke((MethodInvoker) (() => control.Text = "new text"));

或者使用Action委托:

control.Invoke(new Action(() => control.Text = "new text"));

在这里查看两者的比较:方法调用与控制操作

您可以使用已经存在的委托Action

private void UpdateMethod(){if (InvokeRequired){Invoke(new Action(UpdateMethod));}}

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

像这样做一个委托:

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

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

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

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

前面答案中的Invoke内容都不是必需的。

您需要查看WindowsFormsSynChronizationContext:

// In the main threadWindowsFormsSynchronizationContext 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);}}

处理长时间工作

. NET 4.5和C#5.0开始,您应该使用基于任务的异步模式(TAP)同步<强>等待关键字在所有领域(包括GUI):

TAP是新开发推荐的异步设计模式

而不是异步编程模型(APM)基于事件的异步模式(EAP)(后者包括背景工人阶级)。

然后,新开发的推荐解决方案是:

  1. 事件处理程序的异步实现(是的,就是这样):

     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";}
  2. 通知UI线程的第二个线程的实现:

     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());}}}

注意以下几点:

  1. 以顺序方式编写的简短而干净的代码,没有回调和显式线程。
  2. 任务而不是线程
  3. async关键字,允许使用等待,这反过来阻止事件处理程序到达完成状态,直到任务完成,同时不会阻塞UI线程。
  4. 支持关注点分离(SoC)设计原则并且不需要显式调度程序和调用的进度类(参见I进步接口)。它使用其创建位置(这里是UI线程)的当前同步上下文
  5. 任务创建选项。长期运行,提示不要将任务排队到线程池

有关更详细的示例,请参阅:C#的未来:好东西会降临到那些“等待”的人身上 byJoseph Albahari

另见UI线程模型概念。

处理异常

下面的代码片段是如何处理异常和切换按钮的Enabled属性以防止后台执行期间多次单击的示例。

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; });

我比较喜欢这个:

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

我想添加一个警告,因为我注意到一些简单的解决方案省略了InvokeRequired检查。

我注意到,如果您的代码执行在创建控件的窗口句柄之前(例如在显示表单之前),Invoke会抛出异常。所以我建议在调用InvokeBeginInvoke之前始终检查InvokeRequired

我认为最简单的方法:

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

创建一个类变量:

SynchronizationContext _context;

在创建UI的构造函数中设置它:

var _context = SynchronizationContext.Current;

当您想更新标签时:

_context.Send(status =>{// UPDATE LABEL}, null);

例如,访问当前线程中以外的控件:

Speed_Threshold = 30;textOutput.Invoke(new EventHandler(delegate{lblThreshold.Text = Speed_Threshold.ToString();}));

其中lblThreshold是一个标签,Speed_Threshold是一个全局变量。

也许有点过量,但这是我通常解决这个问题的方式:

由于同步,此处不需要调用。基本类线程示例对我来说只是一种布局,所以改变它以适应你的实际需要。

这很简单,因为您不需要处理UI线程中的内容!

public partial class Form1 : Form{BasicClassThreadExample _example;
public Form1(){InitializeComponent();_example = new BasicClassThreadExample();_example.MessageReceivedEvent += _example_MessageReceivedEvent;}
void _example_MessageReceivedEvent(string command){listBox1.Items.Add(command);}
private void button1_Click(object sender, EventArgs e){listBox1.Items.Clear();_example.Start();}}
public class BasicClassThreadExample : IDisposable{public delegate void MessageReceivedHandler(string msg);
public event MessageReceivedHandler MessageReceivedEvent;
protected virtual void OnMessageReceivedEvent(string msg){MessageReceivedHandler handler = MessageReceivedEvent;if (handler != null){handler(msg);}}
private System.Threading.SynchronizationContext _SynchronizationContext;private System.Threading.Thread _doWorkThread;private bool disposed = false;
public BasicClassThreadExample(){_SynchronizationContext = System.ComponentModel.AsyncOperationManager.SynchronizationContext;}
public void Start(){_doWorkThread = _doWorkThread ?? new System.Threading.Thread(dowork);
if (!(_doWorkThread.IsAlive)){_doWorkThread = new System.Threading.Thread(dowork);_doWorkThread.IsBackground = true;_doWorkThread.Start();}}
public void dowork(){string[] retval = System.IO.Directory.GetFiles(@"C:\Windows\System32", "*.*", System.IO.SearchOption.TopDirectoryOnly);foreach (var item in retval){System.Threading.Thread.Sleep(25);_SynchronizationContext.Post(new System.Threading.SendOrPostCallback(delegate(object obj){OnMessageReceivedEvent(item);}), null);}}
protected virtual void Dispose(bool disposing){if (!disposed){if (disposing){_doWorkThread.Abort();}disposed = true;}}
public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}
~BasicClassThreadExample() { Dispose(false); }
}

当您在UI线程中时,您可以向它询问其同步上下文任务调度程序。它会给你一个任务调度来调度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;}}

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

绝大多数答案使用Control.Invoke,这是等待发生的竞赛条件。例如,考虑接受的答案:

string newText = "abc"; // running on worker threadthis.Invoke((MethodInvoker)delegate {someLabel.Text = newText; // runs on UI thread});

如果用户在调用this.Invoke之前关闭表单(请记住,thisForm对象),则可能会触发ObjectDisposedException

解决方案是使用SynchronizationContext,特别是hamilton.danielb建议的SynchronizationContext.Current(其他答案依赖于特定的SynchronizationContext实现,这是完全不必要的)。不过,我会稍微修改他的代码以使用SynchronizationContext.Post而不是SynchronizationContext.Send(因为通常不需要工作线程等待):

public partial class MyForm : Form{private readonly SynchronizationContext _context;public MyForm(){_context = SynchronizationContext.Current...}
private MethodOnOtherThread(){..._context.Post(status => someLabel.Text = newText,null);}}

请注意,在. NET 4.0及更高版本上,您应该真正使用任务进行异步操作。有关等效的基于任务的方法(使用TaskScheduler.FromCurrentSynchronizationContext),请参阅n-san的答案。

最后,在. NET 4.5及更高版本上,您还可以使用Progress<T>(基本上在创建时捕获SynchronizationContext.Current),如里沙德·德泽根的所示,用于长期运行的操作需要在仍在工作时运行UI代码的情况。

为了在WPF中实现这一点,我采用以下方法。

 new Thread(() =>{while (...){SomeLabel.Dispatcher.BeginInvoke((Action)(() => SomeLabel.Text = ...));}}).Start();

我刚刚阅读了答案,这似乎是一个非常热门的话题。我目前正在使用。NET 3.5 SP1和Windows窗体。

前面的答案中大大描述的众所周知的公式使用了调用参数属性,涵盖了大多数情况,但不是整个池。

如果手柄还没有创建呢?

调用参数属性,如此处(Control. InvokeInvoke对MSDN的属性引用)所述,如果调用是从不是GUI线程的线程进行的,则返回true,如果调用是从GUI线程进行的,或者如果手柄尚未创建,则返回false。

如果您想让另一个线程显示和更新模态表单,您可能会遇到异常。因为您希望以模态方式显示该表单,所以您可以执行以下操作:

private MyForm _gui;
public void StartToDoThings(){_gui = new MyForm();Thread thread = new Thread(SomeDelegate);thread.Start();_gui.ShowDialog();}

委托可以更新GUI上的标签:

private void SomeDelegate(){// Operations that can take a variable amount of time, even no time//... then you update the GUIif(_gui.InvokeRequired)_gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });else_gui.Label1.Text = "Done!";}

如果标签更新之前的操作比GUI线程创建形式手柄所需的时间“花费更少的时间”(阅读并将其解释为简化),这可能会导致无效的操作异常。这发生在展示对话框方法中。

您还应该检查手柄,如下所示:

private void SomeDelegate(){// Operations that can take a variable amount of time, even no time//... then you update the GUIif(_gui.IsHandleCreated)  //  <---- ADDEDif(_gui.InvokeRequired)_gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });else_gui.Label1.Text = "Done!";}

如果手柄尚未创建,您可以处理要执行的操作:您可以忽略GUI更新(如上面的代码所示),也可以等待(风险更大)。这应该回答这个问题。

可选的东西:我写了以下代码:

public class ThreadSafeGuiCommand{private const int SLEEPING_STEP = 100;private readonly int _totalTimeout;private int _timeout;
public ThreadSafeGuiCommand(int totalTimeout){_totalTimeout = totalTimeout;}
public void Execute(Form form, Action guiCommand){_timeout = _totalTimeout;while (!form.IsHandleCreated){if (_timeout <= 0) return;
Thread.Sleep(SLEEPING_STEP);_timeout -= SLEEPING_STEP;}
if (form.InvokeRequired)form.Invoke(guiCommand);elseguiCommand();}}

我为另一个线程更新的表单提供了一个线程安全规范的实例,我定义了更新GUI(在我的表单中)的方法,如下所示:

public void SetLabeTextTo(string value){_threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; });}

通过这种方式,我很确定我将更新我的GUI,无论线程将进行调用,可选地等待定义良好的时间量(超时)。

基本上,无论框架版本或GUI底层库类型如何,解决此问题的方法都是保存为工作线程创建线程的同步上下文的控件,该控件将列出从工作线程到GUI线程消息队列的控件相关交互。

示例:

SynchronizationContext ctx = SynchronizationContext.Current; // From controlctx.Send\Post... // From worker thread

在我的情况下(WPF),解决方案很简单:

private void updateUI(){if (!Dispatcher.CheckAccess()){Dispatcher.BeginInvoke(updateUI);return;}
// Update any number of controls here}

即使操作很耗时(在我的示例中thread.sleep)-此代码也不会锁定您的UI:

 private void button1_Click(object sender, EventArgs e){
Thread t = new Thread(new ThreadStart(ThreadJob));t.IsBackground = true;t.Start();}
private void ThreadJob(){string newValue= "Hi";Thread.Sleep(2000);
this.Invoke((MethodInvoker)delegate{label1.Text = newValue;});}

我无法理解这个丑陋实现背后的微软逻辑,但你必须有两个功能:

void setEnableLoginButton(){if (InvokeRequired){// btn_login can be any conroller, (label, button textbox ..etc.)
btn_login.Invoke(new MethodInvoker(setEnable));
// OR//Invoke(new MethodInvoker(setEnable));}else {setEnable();}}
void setEnable(){btn_login.Enabled = isLoginBtnEnabled;}

这些片段为我工作,所以我可以在另一个线程上做一些事情,然后我更新GUI:

Task.Factory.StartNew(()=>{// THIS IS NOT GUIThread.Sleep(5000);// HERE IS INVOKING GUIbtn_login.Invoke(new Action(() => DoSomethingOnGUI()));});
private void DoSomethingOnGUI(){// GUIMessageBox.Show("message", "title", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);}

更简单:

btn_login.Invoke(new Action(()=>{ /* HERE YOU ARE ON GUI */ }));

简单地使用这样的东西:

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

这是一个使用更功能化风格的古老问题的新外观。如果您在所有项目中保留TaskXM类,您只有一行代码,再也不用担心跨线程更新了。

public class Example{/// <summary>/// No more delegates, background workers, etc. Just one line of code as shown below./// Note it is dependent on the Task Extension method shown next./// </summary>public async void Method1(){// Still on the GUI thread here if the method was called from the GUI thread// This code below calls the extension method which spins up a new task and calls back.await TaskXM.RunCodeAsync(() =>{// Running an asynchronous task here// Cannot update the GUI thread here, but can do lots of work});// Can update GUI on this line}}

/// <summary>/// A class containing extension methods for the Task class/// </summary>public static class TaskXM{/// <summary>/// RunCodeAsyc is an extension method that encapsulates the Task.run using a callback/// </summary>/// <param name="Code">The caller is called back on the new Task (on a different thread)</param>/// <returns></returns>public async static Task RunCodeAsync(Action Code){await Task.Run(() =>{Code();});return;}}

WPF应用程序中最简单的方法是:

this.Dispatcher.Invoke((Action)(() =>{// This refers to a form in a WPF applicationval1 = textBox.Text; // Access the UI}));

在这个问题上,大多数其他答案对我来说都有点复杂(我是C#新手),所以我写了我的:

我有一个WPF应用程序,并定义了一个工作人员如下:

问题:

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 UItxt.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 workstxtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));}

我还没有弄清楚上面的线是什么意思,但它有效。

对于WinForms

解决方案:

txtLog.Invoke((MethodInvoker)delegate{txtLog.Text = "my message";});

首先获取表单的实例(在本例中为main Form),然后在另一个线程中使用此代码。

mainForm.Invoke(new MethodInvoker(delegate (){// Update things in my mainForm heremainForm.UpdateView();}));

关于这个主题的另一个例子:我创建了一个抽象类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 codethis.RunOnGuiThread(() => {// Do something over UI controls});});
this.CompleteTask(task, TaskContinuationOptions.OnlyOnRanToCompletion, delegate{// Code that can safely use UI controls});

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

示例:

public  class data_holder_for_controls{// It will hold the value for your labelpublic 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 functionfor (int i = 0; i < 10; i++){// Statements here}// Set the result in the status variabled1.status = "Task done";}}

最简单的方法是调用如下:

 Application.Current.Dispatcher.Invoke(new Action(() =>{try{///}catch (Exception){//}

}));

还有另一个通用的Control扩展aproach…

首先为类型控制的对象添加扩展方法

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);}}

并从另一个线程像这样调用以访问UI线程中名为Object t1的Control:

object1.InvokeIfRequired(c => { c.Visible = true; });object1.InvokeIfRequired(c => { c.Text = "ABC"; });

…或者像这样

object1.InvokeIfRequired(c =>{c.Text = "ABC";c.Visible = true;});

一般方法是这样的:

using System;using System.Threading;using System.Windows.Forms;
namespace WindowsFormsApp1{public partial class Form1 : Form{int clickCount = 0;
public Form1(){InitializeComponent();label1.SetText("0");}
private void button1_Click(object sender, EventArgs e){new Thread(() => label1.SetText((++clickCount).ToString())).Start();}}
public static class ControlExtensions{public static void SetText(this Control control, string text){if (control.InvokeRequired)control.Invoke(setText, control, text);elsecontrol.Text = text;}
private static readonly Action<Control, string> setText =(control, text) => control.Text = text;}}

补充说明

答案很像control0。但使用了更整洁(对我来说)和更新的语法。重点是controlInvokeRequired属性。它获得一个值,指示调用者在对控件进行方法调用时是否必须调用调用方法,因为调用者在与创建控件的线程不同的线程上。因此,如果我们在创建control的同一线程上调用control.SetText("some text"),将Text设置为control.Text = text是可以的。但在任何其他线程上,它都会导致System.InvalidOperationException,因此必须通过control.Invoke(...)调用方法来设置control创建的线程上的Text

只需使用用户界面的同步上下文

using System.Threading;
// ...
public partial class MyForm : Form{private readonly SynchronizationContext uiContext;
public MyForm(){InitializeComponent();uiContext = SynchronizationContext.Current; // get ui thread context}
private void button1_Click(object sender, EventArgs e){Thread t = new Thread(() =>{// set ui thread context to new thread context// for operations with ui elements to be performed in proper threadSynchronizationContext.SetSynchronizationContext(uiContext);label1.Text = "some text";});t.Start();}}