从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
当前回答
我的版本是插入一行递归的“咒语”:
对于无参数:
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()语句后一行放置{}对代码可读性不利。但在这种情况下,这是例行的“咒语”。如果此方法在项目中保持一致,则不会破坏代码的可读性。它还可以避免代码乱丢(一行代码而不是五行代码)。
当您看到(InvokeRequired){some long}时,您只知道“从另一个线程调用此函数是安全的”。
其他回答
这是一个古老问题的新视角,使用了更实用的风格。如果您在所有项目中都保留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 application
val1 = textBox.Text; // Access the UI
}));
我无法理解这一丑陋实现背后的微软逻辑,但你必须有两个功能:
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 */ }));
创建类变量:
SynchronizationContext _context;
在创建UI的构造函数中设置它:
var _context = SynchronizationContext.Current;
要更新标签时:
_context.Send(status =>{
// UPDATE LABEL
}, null);
Label lblText; //initialized elsewhere
void AssignLabel(string text)
{
if (InvokeRequired)
{
BeginInvoke((Action<string>)AssignLabel, text);
return;
}
lblText.Text = text;
}
请注意,BeginInvoke()比Invoke(()更受欢迎,因为它不太可能导致死锁(然而,在这里,将文本分配给标签时,这不是问题):
使用Invoke()时,您正在等待方法返回。现在,可能是您在调用的代码中执行了一些需要等待线程的操作,如果它隐藏在您正在调用的某些函数中,这可能不会立即显现出来,而这本身可能会通过事件处理程序间接发生。因此,您将等待线程,线程将等待您,并且您处于死锁状态。
这实际上导致了我们发布的一些软件挂起。用BeginInvoke()替换Invoke(。除非需要同步操作(如果需要返回值,则可能是这种情况),否则请使用BeginInvoke()。