// 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
如果您使用的是. 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);
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();}}}
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);}}}
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);}
#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
// 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}
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 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!";}
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!";}
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();}}
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 */ }));
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;}}
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";});
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";}}
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);}}
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();}}