目前我正在使用C#中的WinForms,并且我需要在后台运行应用程序。为此,我使用异步。当我运行应用程序时,它会显示一个异常,如下所示:
"跨线程操作无效: 从创建它的线程以外的线程访问控件''。"
我该如何解决这个错误?
目前我正在使用C#中的WinForms,并且我需要在后台运行应用程序。为此,我使用异步。当我运行应用程序时,它会显示一个异常,如下所示:
"跨线程操作无效: 从创建它的线程以外的线程访问控件''。"
我该如何解决这个错误?
在调用控件的方法时,如果调用者所在的线程与创建该控件的线程不同,则需要使用Control.Invoke方法进行调用。以下是一个代码示例:
// you can define a delegate with the signature you want
public delegate void UpdateControlsDelegate();
public void SomeMethod()
{
//this method is executed by the background worker
InvokeUpdateControls();
}
public void InvokeUpdateControls()
{
if (this.InvokeRequired)
{
this.Invoke(new UpdateControlsDelegate(UpdateControls));
}
else
{
UpdateControls();
}
}
private void UpdateControls()
{
// update your controls here
}
希望能对你有所帮助。
您需要检查您尝试更新的控件是否需要调用。可以这样做:
Action<Control, string> setterCallback = (toSet, text) => toSet.Text = text;
void SetControlText(Control toSet, string text) {
if (this.InvokeRequired) {
this.Invoke(setterCallback, toSet, text);
}
else {
setterCallback(toSet, text);
}
}
public delegate void InvocationDelegate();
public void DoGuiStuff(){
if (someControl.InvokeRequired){
someControl.Invoke(InvocationDelegate(DoGuiStuff));
return;
}
//GUI manipulation here
}
从Invoke更新为begin Invoke
// you can define a delegate with the signature you want
public delegate void UpdateControlsDelegate();
public void SomeMethod()
{
//this method is executed by the background worker
InvokeUpdateControls();
}
public void InvokeUpdateControls()
{
if (this.InvokeRequired)
{
this.BeginInvoke(new UpdateControlsDelegate(UpdateControls));
}
else
{
UpdateControls();
}
}
private void UpdateControls()
{
// update your controls here
}
InvokeUpdateControls
有点危险。如果需要调用Invoke
,那么你正在异步执行委托,即将其安排到以后的某个时间执行,但是如果你正在同一线程上(或窗口句柄尚未创建),则立即在那里执行它。这可能是预期的,也可能不是,但应该记住这种区别。 - nawfalUI的变化可以通过Control.Invoke()方法来完成,这个跨线程异常可以使用下面的代码片段来解决。
void UpdateWorker()
{
//Here ddUser is the user control
//Action to be performed should be called within { } as like below code
if (this.ddUser.InvokeRequired)
ddUser.Invoke(new MethodInvoker(() => { ddUser.Size = new Size(100, 100); }));
}
BeginInvoke
这是一种很好的方法,可以防止跨线程异常。我在书籍《C#程序员学习指南(MCSD)》中读到过。
您可以使用BeginInvoke
BeginInvoke方法用于从其他线程更改UI控件的值。它以线程安全的方式执行此操作。它需要一个委托,告诉它哪个UI控件需要更改其值。
private async void button1_Click(object sender, EventArgs e)
{
Task task = Task.Run(() =>
{
this.BeginInvoke(new Action(() =>
{
label1.Text = "Hello";
}));
});
await task;
}
的值将被更改为“Hello”,由于它是线程安全的操作,因此不会出现异常。我知道这个话题已经有10年了,但是我想通过lambda选择器来改进通用解决方案,而不是定义每种类型的setter。
private void SetControlSafety<C, V>(C control, Expression<Func<C, V>> selector, V value)
{
if (this.InvokeRequired)
this.Invoke(MyUtils.GetSetter(selector), control, value);
else
DataCrawlerUtils.GetSetter(selector)(control, value);
}
或者是静态的
public static void SetControlSafety<C, V>(C control, Expression<Func<C, V>> selector, V value) where C : Control
{
if (control.InvokeRequired)
control.Invoke(DataCrawlerUtils.GetSetter(selector), control, value);
else
DataCrawlerUtils.GetSetter(selector)(control, value);
}
通过lambda选择了从这里获取值并赋给属性的GetSetter方法
public static Action<T, TProperty> GetSetter<T, TProperty>(
Expression<Func<T, TProperty>> pExpression
)
{
var parameter1 = Expression.Parameter(typeof(T));
var parameter2 = Expression.Parameter(typeof(TProperty));
// turning an expression body into a PropertyInfo is common enough
// that it's a good idea to extract this to a reusable method
var member = (MemberExpression)pExpression.Body;
var propertyInfo = (PropertyInfo)member.Member;
// use the PropertyInfo to make a property expression
// for the first parameter (the object)
var property = Expression.Property(parameter1, propertyInfo);
// assignment expression that assigns the second parameter (value) to the property
var assignment = Expression.Assign(property, parameter2);
// then just build the lambda, which takes 2 parameters, and has the assignment
// expression for its body
var setter = Expression.Lambda<Action<T, TProperty>>(
assignment,
parameter1,
parameter2
);
return setter.Compile();
}
然后使用起来非常简单
SetControlSafety(txtStatus, x => x.Text, "Loading resources...");