如何从C#中的另一个线程更新GUI?
什么是从另一个线程更新Label
最简单的方法?
我有一个Form
上的thread1
,并从那我开始另一个线程( thread2
)。 虽然thread2
正在处理一些文件,我想用thread2
的当前状态更新Form
上的Label
。
我怎样才能做到这一点?
对于.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).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和lambdaexpression式来允许更干净,更简单和更安全的语法:
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile
现在不仅在编译时检查属性名称,属性的types也是如此,所以不可能(例如)将一个string值赋给布尔属性,从而导致运行时exception。
不幸的是,这并不能阻止任何人做愚蠢的事情,比如传递另一个Control
的属性和价值,所以下面的代码会很愉快的编译:
myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
因此,我添加了运行时检查,以确保传入的属性确实属于调用该方法的Control
。 不完美,但仍比.NET 2.0版本好很多。
如果有人有进一步的build议,如何改善这个代码的编译时安全性,请评论!
最简单的方法是传递给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
阻塞执行直到完成 – 这是同步代码。 这个问题没有询问关于asynchronous代码的问题,但是当你想了解它时,Stack Overflow上有很多关于编写asynchronous代码的内容。
处理长时间的工作
从.NET 4.5和C#5.0开始,您应该在所有区域 (包括GUI)中使用基于任务的asynchronous模式(TAP)以及async – await关键字:
TAP是推荐的用于新开发的asynchronousdevise模式
而不是asynchronous编程模型(APM)和基于事件的asynchronous模式(EAP) (后者包括BackgroundWorker类 )。
那么,新开发的推荐解决scheme是:
-
事件处理程序的asynchronous实现(是的,就是这样):
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"; }
-
执行通知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()); } } }
注意以下几点:
- 简洁而干净的代码以顺序的方式编写,无需callback和显式线程。
- 任务而不是线程 。
- async关键字,它允许使用await进而阻止事件处理程序到达完成状态,直到任务完成,同时不会阻塞UI线程。
- 支持分离关注(SoC)devise原则的Progress类(参见IProgress接口 ),不需要显式调度和调用。 它从创build位置(这里是UI线程)使用当前的SynchronizationContext 。
- TaskCreationOptions.Long运行提示不要将任务排入ThreadPool 。
有关更详细的示例,请参阅: C#的未来:好消息来自 Joseph Albahari “等待”的人 。
另请参阅有关UI线程模型的概念。
处理exception
下面的片段是如何处理exception和切换button的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..."); } }
Marc Gravell 最简单的.NET 4 解决scheme的变体:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
或者使用Action委托代替:
control.Invoke(new Action(() => control.Text = "new text"));
请参阅这里比较两者: MethodInvoker vs Action for Control.BeginInvoke
火,忘了.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");
这是你应该这样做的经典方式:
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中,你需要通过线程来改变实际的控制…就像一个标签或一个进度条。
简单的解决scheme是使用Control.Invoke
。
void DoSomething() { if (InvokeRequired) { Invoke(new MethodInvoker(updateGUI)); } else { // Do Something updateGUI(); } } void updateGUI() { // update gui here }
线程代码通常很麻烦,而且总是很难testing。 您不需要编写线程代码来更新后台任务的用户界面。 只需使用BackgroundWorker类运行任务及其ReportProgress方法即可更新用户界面。 通常,只报告一个完整的百分比,但还有另一个包含状态对象的重载。 以下是一个只报告string对象的例子:
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
之前closures表单(记住, this
是Form
对象), ObjectDisposedException
可能会被触发。
解决scheme是使用SynchronizationContext
,特别是SynchronizationContext
,如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及更高版本中,您应该真正使用asynchronous操作的任务。 请参阅n-san针对基于等效任务的方法的回答(使用TaskScheduler.FromCurrentSynchronizationContext
)。
最后,在.NET 4.5及更高版本中,您还可以使用Progress<T>
(基本上在创buildSynchronizationContext.Current
时捕获它),如RyszardDżegan所示 ,对于长时间运行需要在运行时仍需运行UI代码的情况。
您必须确保更新发生在正确的线程上; UI线程。
为了做到这一点,你必须调用事件处理程序,而不是直接调用它。
你可以通过提高你的事件来做到这一点:
(代码是在我的脑海里input的,所以我没有检查正确的语法等,但它应该让你去。)
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
类。
为了以这种方式轻松地引发事件,我创build了一个扩展方法,这个方法允许我简单地调用一个事件来简化:
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; }
在以前的答案中没有任何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 }
由于场景的琐碎我实际上有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
是一个阻塞调用。 它将暂时停止正在该线程中完成的工作。
我在这个答案中提出的策略颠倒了线程的沟通angular色。 而不是工作线程推送数据的UI线程轮询它。 这是许多情况下使用的常见模式。 因为你所要做的只是显示工作线程的进度信息,所以我认为你会发现这个解决scheme是编组解决scheme的一个很好的select。 它具有以下优点。
- UI和工作线程保持松散耦合,而不是紧密耦合它们的
Control.Invoke
或Control.BeginInvoke
方法。 - UI线程不会妨碍工作线程的进度。
- 工作线程不能支配UI线程花费更新的时间。
- UI和工作线程执行操作的间隔可以保持独立。
- 工作线程不能溢出UI线程的消息泵。
- 用户界面线程可以决定何时更新UI的频率。
对于很多目的来说,这很简单:
public delegate void serviceGUIDelegate(); private void updateGUI() { this.Invoke(new serviceGUIDelegate(serviceGUI)); }
“serviceGUI()”是窗体(this)中的GUI级别方法,可以根据需要更改任意数量的控件。 从另一个线程调用“updateGUI()”。 可以添加参数来传递值,或者(可能更快)使用类作用域variables,并根据需要locking它们,如果在访问它们的线程之间有任何可能导致不稳定的可能性。 如果非GUI线程时间关键(使用Brian Gideon的警告),请使用BeginInvoke而不是Invoke。
这在我的C#3.0变化的伊恩·坎普的解决scheme:
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!")
- 它将“作为MemberExpression”的结果添加空检查。
- 它改善了静态型安全性。
否则,原来是一个非常好的解决scheme。
Label lblText; //initialized elsewhere void AssignLabel(string text) { if (InvokeRequired) { BeginInvoke((Action<string>)AssignLabel, text); return; } lblText.Text = text; }
请注意, BeginInvoke()
优于Invoke()
因为它不太可能导致死锁(但是,在将文本分配给标签时,这不是问题):
当使用Invoke()
你正在等待方法返回。 现在,可能是你在被调用的代码中做了一些需要等待这个线程的东西,如果它被埋在你正在调用的某些函数中,这个函数可能不是很明显,而这些函数本身可能通过事件处理程序间接发生。 所以你会等待的线程,线程将等待你,你是僵持的。
这实际上导致我们发布的一些软件挂起。 通过用BeginInvoke()
replaceInvoke()
很容易。 除非需要同步操作,如果您需要返回值,则可能是这种情况,请使用BeginInvoke()
。
Salvete! find这个问题后,我发现FrankG和Oregon Ghost的答案对我来说是最简单最有用的。 现在,我在Visual Basic中编写代码,并通过转换器运行此代码段。 所以我不确定事情的结果如何。
我有一个名为form_Diagnostics,
的对话框form_Diagnostics,
它有一个名为updateDiagWindow,
form_Diagnostics,
框,我用它作为一种日志显示。 我需要能够从所有线程更新文本。 额外的行允许窗口自动滚动到最新的行。
因此,我现在可以用一行,从整个程序中的任何地方以您认为无需任何线程的方式更新显示:
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
这与使用.NET Framework 3.0的上述解决scheme类似,但它解决了编译时安全支持的问题 。
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);
如果用户传递了错误的数据types,编译器将会失败。
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");
当我遇到同样的问题时,我寻求谷歌的帮助,而不是给我一个简单的解决scheme,它给我更多的困惑给我的MethodInvoker
例子,等等等等等等。 所以我决定自己解决。 这是我的解决scheme:
做一个这样的代表:
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(() => .....)
混淆。 我在一个线程上工作时使用匿名函数或lambdaexpression式。 为了减less代码行,你可以使用ThreadStart(..)
方法,这里我不应该解释这个。
简单地使用这样的东西:
this.Invoke((MethodInvoker)delegate { progressBar1.Value = e.ProgressPercentage; // runs on UI thread });
您可以使用已经存在的委托Action
:
private void UpdateMethod() { if (InvokeRequired) { Invoke(new Action(UpdateMethod)); } }
You must use invoke and delegate
private delegate void MyLabelDelegate(); label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
My version is to insert one line of recursive "mantra":
For no arguments:
void Aaaaaaa() { if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra // Your code! }
For a function that has arguments:
void Bbb(int x, string text) { if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; } // Your code! }
THAT is IT .
Some argumentation : Usually it is bad for code readability to put {} after an if ()
statement in one line. But in this case it is routine all-the-same "mantra". It doesn't break code readability if this method is consistent over the project. And it saves your code from littering (one line of code instead of five).
As you see if(InvokeRequired) {something long}
you just know "this function is safe to call from another thread".
Create a class variable:
SynchronizationContext _context;
Set it in the constructor that creates your UI:
var _context = SynchronizationContext.Current;
When you want to update the label:
_context.Send(status =>{ // UPDATE LABEL }, null);
Try to refresh the label using this
public static class ExtensionMethods { private static Action EmptyDelegate = delegate() { }; public static void Refresh(this UIElement uiElement) { uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate); } }
The easiest way I think:
void Update() { BeginInvoke((Action)delegate() { //do your update }); }
For example, access a control other than in the current thread:
Speed_Threshold = 30; textOutput.Invoke(new EventHandler(delegate { lblThreshold.Text = Speed_Threshold.ToString(); }));
There the lblThreshold
is a Label and Speed_Threshold
is a global variable.
I just read the answers and this appears to be a very hot topic. I'm currently using .NET 3.5 SP1 and Windows Forms.
The well-known formula greatly described in the previous answers that makes use of the InvokeRequired property covers most of the cases, but not the entire pool.
What if the Handle has not been created yet?
The InvokeRequired property, as described here (Control.InvokeRequired Property reference to MSDN) returns true if the call was made from a thread that is not the GUI thread, false either if the call was made from the GUI thread, or if the Handle was not created yet.
You can come across an exception if you want to have a modal form shown and updated by another thread. Because you want that form shown modally, you could do the following:
private MyForm _gui; public void StartToDoThings() { _gui = new MyForm(); Thread thread = new Thread(SomeDelegate); thread.Start(); _gui.ShowDialog(); }
And the delegate can update a Label on the GUI:
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!"; }
This can cause an InvalidOperationException if the operations before the label's update "take less time" (read it and interpret it as a simplification) than the time it takes for the GUI thread to create the Form 's Handle . This happens within the ShowDialog() method.
You should also check for the Handle like this:
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!"; }
You can handle the operation to perform if the Handle has not been created yet: You can just ignore the GUI update (like shown in the code above) or you can wait (more risky). This should answer the question.
Optional stuff: Personally I came up coding the following:
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(); } }
I feed my forms that get updated by another thread with an instance of this ThreadSafeGuiCommand , and I define methods that update the GUI (in my Form) like this:
public void SetLabeTextTo(string value) { _threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; }); }
In this way I'm quite sure that I will have my GUI updated whatever thread will make the call, optionally waiting for a well-defined amount of time (the timeout).
I wanted to add a warning because I noticed that some of the simple solutions omit the InvokeRequired
check.
I noticed that if your code executes before the window handle of the control has been created (eg before the form is shown), Invoke
throws an exception. So I recommend always checking on InvokeRequired
before calling Invoke
or BeginInvoke
.
Even if the operation is time-consuming (thread.sleep in my example) – This code will NOT lock your 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; }); }