如何用最简单的方式从另一个线程更新Label
?
我有一个在
thread1
上运行的Form
,从那里我启动了另一个线程(thread2
)。当
thread2
处理一些文件时,我想通过Form
将Label
的当前状态更新为thread2
的工作状态。
我该怎么做呢?
如何用最简单的方式从另一个线程更新Label
?
我有一个在thread1
上运行的Form
,从那里我启动了另一个线程(thread2
)。
当thread2
处理一些文件时,我想通过Form
将Label
的当前状态更新为thread2
的工作状态。
我该怎么做呢?
Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
lblThreshold.Text = Speed_Threshold.ToString();
}));
这里的lblThreshold
是一个标签,而Speed_Threshold
是一个全局变量。
只需使用UI的同步上下文
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 thread
SynchronizationContext
.SetSynchronizationContext(uiContext);
label1.Text = "some text";
});
t.Start();
}
}
我刚刚阅读了答案,发现这是一个非常热门的话题。我目前正在使用.NET 3.5 SP1和Windows Forms。
在之前的答案中描述得非常好的公式使用了InvokeRequired属性,它涵盖了大部分情况,但并不是全部。
如果Handle尚未创建怎么办?
正如此处(Control.InvokeRequired Property reference to MSDN)所述的,InvokeRequired属性会返回true,如果调用是从不是GUI线程的线程进行的,则返回false,如果调用是从GUI线程或Handle尚未创建的线程进行的,则返回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 GUI
if(_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 GUI
if(_gui.IsHandleCreated) // <---- ADDED
if(_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);
else
guiCommand();
}
}
我用这个ThreadSafeGuiCommand的实例来更新另一个线程中的表单,然后我定义了像这样更新GUI(在我的表单中)的方法:
public void SetLabeTextTo(string value)
{
_threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; });
}
void Update()
{
BeginInvoke((Action)delegate()
{
//do your update
});
}
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 */ }));
无论框架版本或GUI底层库类型如何,解决此问题的基本方法是保存创建线程的控件的同步上下文,以便将控件相关交互从工作线程调度到GUI线程消息队列。
例如:
SynchronizationContext ctx = SynchronizationContext.Current; // From control
ctx.Send\Post... // From worker 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;
}
}
Task.Run
和直接调用await Task.Run(() => {...});
有什么区别?这里间接性的优势是什么? - Marc L.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); }
}
我希望添加一个警告,因为我注意到有一些简单的解决方案省略了InvokeRequired
检查。
我注意到如果你的代码在控件的窗口句柄创建之前执行(例如,在表单显示之前),Invoke
会抛出异常。因此,我建议在调用Invoke
或BeginInvoke
之前始终检查InvokeRequired
。
另一个关于这个主题的例子:我创建了一个抽象类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 code
this.RunOnGuiThread(() => {
// Do something over UI controls
});
});
this.CompleteTask(task, TaskContinuationOptions.OnlyOnRanToCompletion, delegate
{
// Code that can safely use UI controls
});