从另一个线程更新Label
的最简单方法是什么?
我在thread1
上有一个Form
,从那里我开始另一个线程( thread2
)。当thread2
正在处理一些文件时,我想更新Form
上的Label
,其中包含thread2
工作的当前状态。
我怎样才能做到这一点?
最简单的方法是传递给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
阻止执行直到完成 - 这是同步代码。这个问题并不是关于异步代码的问题,但是当你想要了解异步代码时,Stack Overflow 上有很多关于编写异步代码的内容。
对于. 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 ||
[email protected]().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 表达式来允许更清晰,更简单和更安全的语法:
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile
现在不仅在编译时检查属性名称,属性的类型也是如此,因此不可能(例如)将字符串值赋给布尔属性,从而导致运行时异常。
不幸的是,这并没有阻止任何人做愚蠢的事情,比如传入另一个Control
的属性和值,所以以下内容将很乐意编译:
myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
因此,我添加了运行时检查,以确保传入的属性确实属于调用该方法的Control
。不完美,但仍然比. NET 2.0 版本好很多。
如果有人对如何为编译时安全性改进此代码有任何进一步的建议,请评论!
从.NET 4.5 和 C#5.0 开始,您应该使用基于任务的异步模式(TAP)和异步 - 等待 所有区域 (包括 GUI)中的关键字:
TAP 是新开发的推荐异步设计模式
而不是异步编程模型(APM)和基于事件的异步模式(EAP) (后者包括BackgroundWorker 类 )。
然后,推荐的新开发解决方案是:
事件处理程序的异步实现(是的,就是全部):
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());
}
}
}
请注意以下事项:
有关更详细的例子,请参阅: 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...");
}
}