Label
从另一个线程更新a的最简单方法是什么?
我有一Form
对thread1
,并从我开始另一个线程(thread2
).虽然thread2
在处理一些文件,我想更新Label
在Form
用的当前状态thread2
的工作.
我怎样才能做到这一点?
在最简单的方法是匿名方法传递到Label.Invoke
:
// Running on the worker thread string newText = "abc"; form.Label.Invoke((MethodInvoker)delegate { // Running on the UI thread form.Label.Text = newText; }); // Back on the worker thread
请注意,Invoke
阻止执行直到完成 - 这是同步代码.这个问题并不是关于异步代码的问题,但是当你想要了解异步代码时,Stack Overflow上有很多关于编写异步代码的内容.
对于.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( Control @this, Expression > property, TResult value); public static void SetPropertyThreadSafe ( this Control @this, Expression > 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 (SetPropertyThreadSafe), new object[] { @this, property, value }); } else { @this.GetType().InvokeMember( propertyInfo.Name, BindingFlags.SetProperty, null, @this, new object[] { value }); } }
它使用LINQ和lambda表达式来允许更清晰,更简单和更安全的语法:
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile
现在不仅在编译时检查属性名称,属性的类型也是如此,因此不可能(例如)将字符串值赋给布尔属性,从而导致运行时异常.
不幸的是,这并没有阻止任何人做愚蠢的事情,比如传递另一个Control
人的财产和价值,所以以下内容将很乐意编译:
myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
因此,我添加了运行时检查,以确保传入的属性确实属于Control
被调用方法的属性.不完美,但仍然比.NET 2.0版本好很多.
如果有人对如何为编译时安全性改进此代码有任何进一步的建议,请评论!
从.NET 4.5和C#5.0开始,您应该使用基于任务的异步模式(TAP)和异步 - 等待所有区域(包括GUI)中的关键字:
TAP是新开发的推荐异步设计模式
而不是异步编程模型(APM)和基于事件的异步模式(EAP)(后者包括BackgroundWorker类).
然后,推荐的新开发解决方案是:
事件处理程序的异步实现(是的,就是全部):
private async void Button_Clicked(object sender, EventArgs e) { var progress = new Progress(s => label.Text = s); await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress), TaskCreationOptions.LongRunning); label.Text = "completed"; }
通知UI线程的第二个线程的实现:
class SecondThreadConcern { public static void LongWork(IProgressprogress) { // Perform a long running work... for (var i = 0; i < 10; i++) { Task.Delay(500).Wait(); progress.Report(i.ToString()); } } }
请注意以下事项:
以顺序方式编写的简短而干净的代码,没有回调和显式线程.
任务而不是线程.
async关键字,允许使用await反过来阻止事件处理程序达到完成状态,直到任务完成,同时不阻止UI线程.
进度类(参见IProgress接口),支持Separation of Concerns(SoC)设计原则,不需要显式调度和调用.它使用来自其创建位置的当前SynchronizationContext(此处为UI线程).
TaskCreationOptions.LongRunning,提示不将任务排入ThreadPool.
有关更详细的例子,请参阅:C#的未来:约瑟夫·阿尔巴哈里"等待"的人会遇到好事.
另请参阅UI线程模型概念.
以下代码段是如何处理异常和切换按钮Enabled
属性以防止在后台执行期间多次单击的示例.
private async void Button_Click(object sender, EventArgs e) { button.Enabled = false; try { var progress = new Progress(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 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..."); } }
Marc Gravell 最简单的.NET 4 解决方案的变化:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
或者使用Action委托代替:
control.Invoke(new Action(() => control.Text = "new text"));
请参阅此处以比较两者:MethodInvoker与Control.BeginInvoke的Action
.NET 3.5+的Fire and forget扩展方法
using System; using System.Windows.Forms; public static class ControlExtensions { ////// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread. /// /// /// 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");
这是你应该这样做的经典方式:
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(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 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中,您需要跨线程来更改实际控件...如标签或进度条.
简单的解决方案是使用Control.Invoke
.
void DoSomething() { if (InvokeRequired) { Invoke(new MethodInvoker(updateGUI)); } else { // Do Something updateGUI(); } } void updateGUI() { // update gui here }
线程代码经常出错并且总是难以测试.您无需编写线程代码来从后台任务更新用户界面.只需使用BackgroundWorker类来运行任务及其ReportProgress方法即可更新用户界面.通常,您只报告完成百分比,但还有另一个包含状态对象的重载.这是一个只报告字符串对象的示例:
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状态并将其传递给ReportProgress方法.
最后一件事,一定要设置WorkerReportsProgress
标志,否则ReportProgress
方法将被完全忽略.
绝大多数答案Control.Invoke
都是等待发生的竞争条件.例如,考虑接受的答案:
string newText = "abc"; // running on worker thread this.Invoke((MethodInvoker)delegate { someLabel.Text = newText; // runs on UI thread });
如果用户在this.Invoke
调用之前关闭表单(记住,this
是Form
对象),ObjectDisposedException
则可能会触发.
解决方案是使用SynchronizationContext
,特别SynchronizationContext.Current
是像hamilton.danielb建议的那样(其他答案依赖于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及更高版本中,您应该使用异步操作的任务.请参阅n-san的答案,了解基于任务的等效方法(使用TaskScheduler.FromCurrentSynchronizationContext
).
最后,在.NET 4.5及更高版本中,您也可以使用Progress
(基本上可以捕获SynchronizationContext.Current
它的创建),如RyszardDżegan所展示的那样,长时间运行的操作需要在仍然工作时运行UI代码.
您必须确保更新发生在正确的线程上; 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,和所有其他平台的作品,你可以看看AsyncOperation
,AsyncOperationManager
和SynchronizationContext
类.
为了以这种方式轻松地引发事件,我创建了一个扩展方法,它允许我通过调用简化来简化事件:
MyEvent.Raise(this, EventArgs.Empty);
当然,您也可以使用BackGroundWorker类,它将为您抽象出这个问题.
您需要在GUI线程上调用该方法.您可以通过调用Control.Invoke来实现.
例如:
delegate void UpdateLabelDelegate (string message); void UpdateLabel (string message) { if (InvokeRequired) { Invoke (new UpdateLabelDelegate (UpdateLabel), message); return; } MyLabelControl.Text = message; }
由于场景的微不足道,我实际上会对状态进行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
和ISynchronizeInvoke.BeginInvoke
方法时所需的编组操作.使用编组技术没有任何问题,但您需要注意几个警告.
确保您不要BeginInvoke
太频繁地呼叫,否则它可能会超出消息泵.
调用Invoke
工作线程是一个阻塞调用.它将暂时停止在该线程中完成的工作.
我在这个答案中提出的策略颠倒了线程的通信角色.UI线程轮询它而不是工作线程推送数据.这是许多场景中使用的常见模式.由于您要做的只是显示工作线程的进度信息,我认为您会发现此解决方案是编组解决方案的绝佳替代方案.它具有以下优点.
而不是在用户界面和工作线程保持松耦合Control.Invoke
或Control.BeginInvoke
的做法,紧密结合他们.
UI线程不会妨碍工作线程的进度.
工作线程不能支配UI线程花费更新的时间.
UI和工作线程执行操作的时间间隔可以保持独立.
工作线程不能超出UI线程的消息泵.
UI线程可以决定UI何时以及多久更新一次.
之前的答案中没有必要的Invoke内容.
您需要查看WindowsFormsSynchronizationContext:
// 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 }
这个类似于上面使用.NET Framework 3.0的解决方案,但它解决了编译时安全支持的问题.
public static class ControlExtension { delegate void SetPropertyValueHandler(Control souce, Expression > selector, TResult value); public static void SetPropertyValue (this Control source, Expression > selector, TResult value) { if (source.InvokeRequired) { var del = new SetPropertyValueHandler (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");
Salvete!在搜索了这个问题之后,我发现FrankG和Oregon Ghost的答案对我来说是最简单的.现在,我在Visual Basic中编码并通过转换器运行此代码段; 所以我不确定结果如何.
我有一个名为的对话框form_Diagnostics,
,其中有一个richtext框,updateDiagWindow,
我将其用作一种日志记录显示.我需要能够从所有线程更新其文本.额外的线条允许窗口自动滚动到最新的线条.
所以,我现在可以用你认为它可以在没有任何线程的情况下工作的方式从整个程序的任何地方用一行更新显示:
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
出于许多目的,它就像这样简单:
public delegate void serviceGUIDelegate(); private void updateGUI() { this.Invoke(new serviceGUIDelegate(serviceGUI)); }
"serviceGUI()"是表单(this)中的GUI级方法,可以根据需要更改任意数量的控件.从另一个线程调用"updateGUI()".可以添加参数来传递值,或者(可能更快)使用带有锁定的类范围变量,如果有可能在访问它们的线程之间发生冲突而导致不稳定.如果非GUI线程是时间关键的,请使用BeginInvoke而不是Invoke(记住Brian Gideon的警告).
这是Ian Kemp解决方案的C#3.0变体:
public static void SetPropertyInGuiThread(this C control, Expression > 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 >, V>)SetPropertyInGuiThread, new object[] { control, property, value } ); else propertyInfo.SetValue(control, value, null); }
你这样称呼它:
myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
它将"检查"添加到"as MemberExpression"的结果中.
它提高了静态类型的安全性.
否则,原来是一个非常好的解决方案.
Label lblText; //initialized elsewhere void AssignLabel(string text) { if (InvokeRequired) { BeginInvoke((Action)AssignLabel, text); return; } lblText.Text = text; }
请注意,这BeginInvoke()
是首选,Invoke()
因为它不太可能导致死锁(但是,仅在将文本分配给标签时,这不是问题):
使用时,Invoke()
您正在等待返回的方法.现在,可能是你在需要等待线程的调用代码中做了一些事情,如果它隐藏在你正在调用的某些函数中,这可能不会立即明显,这本身可能通过事件处理程序间接发生.所以你会等待线程,线程会等你,你就陷入僵局.
这实际上导致我们发布的一些软件挂起.这是很容易通过更换来解决Invoke()
同BeginInvoke()
.除非您需要同步操作(如果需要返回值,可能就是这种情况),请使用BeginInvoke()
.
当我遇到同样的问题时,我向谷歌寻求帮助,但不是给我一个简单的解决方案,而是通过举例MethodInvoker
和等等等等方式让我更加困惑.所以我决定自己解决它.这是我的解决方案:
像这样做一个代表:
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();
不要混淆Thread(() => .....)
.我在线程上工作时使用匿名函数或lambda表达式.为了减少代码行,你也可以使用这个ThreadStart(..)
方法,我不应该在这里解释.
只需使用以下内容:
this.Invoke((MethodInvoker)delegate { progressBar1.Value = e.ProgressPercentage; // runs on UI thread });
您可以使用已存在的委托Action
:
private void UpdateMethod() { if (InvokeRequired) { Invoke(new Action(UpdateMethod)); } }
我的版本是插入一行递归"咒语":
没有参数:
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(Bbb), new[] { x, text }); return; } // Your code! }
那就是IT.
一些论证:通常,if ()
在一行中的语句之后放置{}对于代码可读性是不利的.但在这种情况下,它是常规的"咒语".如果此方法在项目中保持一致,则不会破坏代码可读性.它可以避免您的代码乱扔垃圾(一行代码而不是五行).
如您所见,if(InvokeRequired) {something long}
您只知道"此功能可以安全地从另一个线程调用".
尝试使用此刷新标签
public static class ExtensionMethods { private static Action EmptyDelegate = delegate() { }; public static void Refresh(this UIElement uiElement) { uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate); } }
创建一个类变量:
SynchronizationContext _context;
在创建UI的构造函数中设置它:
var _context = SynchronizationContext.Current;
如果要更新标签:
_context.Send(status =>{ // UPDATE LABEL }, null);
您必须使用invoke和delegate
private delegate void MyLabelDelegate(); label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
在这个问题上,大多数其他答案对我来说有点复杂(我是C#的新手),所以我写的是我的:
我有一个WPF应用程序,并定义了一个worker如下:
问题:
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")); }
我还没有找出上述内容的含义,但它确实有效.
对于WinForms:
解:
txtLog.Invoke((MethodInvoker)delegate { txtLog.Text = "my message"; });
我认为最简单的方法:
void Update() { BeginInvoke((Action)delegate() { //do your update }); }
例如,访问当前线程以外的控件:
Speed_Threshold = 30; textOutput.Invoke(new EventHandler(delegate { lblThreshold.Text = Speed_Threshold.ToString(); }));
有lblThreshold
一个Label,Speed_Threshold
是一个全局变量.
当您在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(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; } }
这适用于任务(而不是线程),这是现在编写并发代码的首选方法.
我刚读了答案,这似乎是一个非常热门的话题.我目前正在使用.NET 3.5 SP1和Windows Forms.
在前面的答案中大量描述的众所周知的公式使用了InvokeRequired属性,涵盖了大多数情况,但不包括整个池.
如果还没有创建Handle怎么办?
该InvokeRequired属性,描述在这里(Control.InvokeRequired物业参考MSDN)如果呼叫从一个线程不是GUI线程,假做返回true或者如果呼叫是从GUI线程做,或者如果手柄是尚未创建.
如果您希望另一个线程显示和更新模式表单,则可能会遇到异常.因为您希望以模态方式显示该表单,所以您可以执行以下操作:
private MyForm _gui; public void StartToDoThings() { _gui = new MyForm(); Thread thread = new Thread(SomeDelegate); thread.Start(); _gui.ShowDialog(); }
代理可以在GUI上更新Label:
private void SomeDelegate() { // Operations that can take a variable amount of time, even no time //... then you update the GUI if(_gui.InvokeRequired) _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; }); else _gui.Label1.Text = "Done!"; }
如果标签更新之前的操作"花费更少的时间"(读取并将其解释为简化),则会导致InvalidOperationException,而不是GUI线程创建Form的句柄所花费的时间.这发生在ShowDialog()方法中.
您还应该像这样检查Handle:
private void SomeDelegate() { // Operations that can take a variable amount of time, even no time //... then you update the GUI if(_gui.IsHandleCreated) // <---- ADDED if(_gui.InvokeRequired) _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; }); else _gui.Label1.Text = "Done!"; }
如果还没有创建Handle,您可以处理要执行的操作:您可以忽略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); else guiCommand(); } }
我提供的表单由另一个具有此ThreadSafeGuiCommand实例的线程更新,我定义了更新GUI(在我的表单中)的方法,如下所示:
public void SetLabeTextTo(string value) { _threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; }); }
通过这种方式,我非常确定我会更新任何线程进行调用的GUI,可选择等待明确定义的时间(超时).
即使操作很耗时(在我的示例中为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; }); }
WPF应用程序中最简单的方法是:
this.Dispatcher.Invoke((Action)(() => { // This refers to a form in a WPF application val1 = textBox.Text; // Access the UI }));
我想添加警告,因为我注意到一些简单的解决方案省略了InvokeRequired
检查.
我注意到如果你的代码在创建控件的窗口句柄之前执行(例如在显示表单之前),则Invoke
抛出异常.所以我建议InvokeRequired
在打电话之前一直检查Invoke
或BeginInvoke
.
我无法在这个丑陋的实现背后得到微软的逻辑,但你必须有两个功能:
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 GUI Thread.Sleep(5000); // HERE IS INVOKING GUI btn_login.Invoke(new Action(() => DoSomethingOnGUI())); }); private void DoSomethingOnGUI() { // GUI MessageBox.Show("message", "title", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); }
更简单:
btn_login.Invoke(new Action(()=>{ /* HERE YOU ARE ON GUI */ }));
也许有些过量,但这是我通常解决此问题的一种方式:
由于同步,此处不需要调用。BasicClassThreadExample对我来说只是一种布局,因此请更改它以适合您的实际需求。
这很简单,因为您不需要处理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); } }
这是使用更实用的样式的古老问题的新外观。如果将TaskXM类保留在所有项目中,则只有一行代码,永远不必担心跨线程更新。
public class Example { ////// 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. /// 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 } } ////// A class containing extension methods for the Task class /// public static class TaskXM { ////// RunCodeAsyc is an extension method that encapsulates the Task.run using a callback /// /// The caller is called back on the new Task (on a different thread) ///public async static Task RunCodeAsync(Action Code) { await Task.Run(() => { Code(); }); return; } }
关于该主题的另一个示例:我制作了一个抽象类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, Actionaction) { 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 });