我如何从另一个线程更新GUI?
从另一个线程更新Label
的最简单方法是什么?
我在thread1
上有一个Form
,然后从另一个线程( thread2
)开始。 虽然thread2
正在处理一些文件,但我想用thread2
的当前状态更新Form
上的Label
。
我怎样才能做到这一点?
对于.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);
UPDATE 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 ||
!@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表达式来允许更清晰,更简单和更安全的语法:
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版本好很多。
如果任何人有进一步的建议,如何改进此代码的编译时安全性,请评论!
最简单的方法是传递给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 4.5和C#5.0开始,您应该在所有区域 (包括GUI)中使用基于任务的异步模式(TAP)以及异步 - 等待关键字:
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#的未来:好消息来自Joseph Albahari“等待”的人。
另请参阅有关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...");
}
}
链接地址: http://www.djcxy.com/p/8825.html