从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
当前回答
Marc Gravell的.NET 4最简单解决方案的变体:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
或者改用Action委托:
control.Invoke(new Action(() => control.Text = "new text"));
请参阅此处了解两者的比较:MethodInvoker与Action for Control.BeginInvoke
其他回答
这是Ian Kemp解决方案的C#3.0变体:
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!")
它将空检查添加到“as MemberExpression”的结果中。它提高了静态类型的安全性。
否则,原始版本是一个非常好的解决方案。
我的版本是插入一行递归的“咒语”:
对于无参数:
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}时,您只知道“从另一个线程调用此函数是安全的”。
例如,访问当前线程以外的控件:
Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
lblThreshold.Text = Speed_Threshold.ToString();
}));
lblThreshold是一个标签,Speed_Threshold则是一个全局变量。
我更喜欢这个:
private void UpdateNowProcessing(string nowProcessing)
{
if (this.InvokeRequired)
{
Action<string> d = UpdateNowProcessing;
Invoke(d, nowProcessing);
}
else
{
this.progressDialog.Next(nowProcessing);
}
}
处理长工作
由于.NET 4.5和C#5.0,您应该在所有领域(包括GUI)使用基于任务的异步模式(TAP)和异步等待关键字:
TAP是新开发推荐的异步设计模式
而不是异步编程模型(APM)和基于事件的异步模式(EAP)(后者包括BackgroundWorker类)。
那么,新开发的建议解决方案是:
事件处理程序的异步实现(是的,仅此而已):私有异步void Button_Clicked(对象发送方,EventArgs e){var progress=new progress<string>(s=>label.Text=s);await Task.Factory.StartNew(()=>SecondThreadConcern.LongWork(进度),TaskCreationOptions.LongRunning);label.Text=“已完成”;}通知UI线程的第二个线程的实现:类SecondThreadConcern{公共静态void LongWork(IProgress<string>进度){//进行长时间的工作。。。对于(变量i=0;i<10;i++){任务.延迟(500).等待();progress.Report(i.ToString());}}}
请注意以下事项:
以顺序方式编写的简短而干净的代码,没有回调和显式线程。任务而不是线程。async关键字,它允许使用await,从而防止事件处理程序在任务完成之前达到完成状态,同时不会阻塞UI线程。进程类(参见IProgress接口),支持关注点分离(SoC)设计原则,不需要显式调度程序和调用。它使用创建位置(此处为UI线程)的当前SynchronizationContext。TaskCreationOptions.LongRunning,提示不要将任务排入ThreadPool。
有关更详细的示例,请参阅约瑟夫·阿尔巴哈里(Joseph Albahari)的《C#的未来:美好的事物降临在那些“等待”的人身上》。
另请参见UI线程模型概念。
异常处理
下面的代码片段是如何处理异常和切换按钮的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...");
}
}