如何在没有control . invoke()的情况下从后台线程修改控件属性?
本文关键字:后台 情况下 线程 修改 属性 控件 control invoke | 更新日期: 2023-09-27 18:12:52
最近我们遇到了一些旧的WinForms应用程序,我们需要更新一些新功能。当专家测试应用程序时,发现一些旧的功能被破坏了。无效的跨线程操作。在你认为我是个新手之前,我确实有一些使用Windows窗体应用程序的经验。我不是专家,但我认为自己在线程方面经验丰富,并且确切地知道如何从工作线程操作GUI组件。
显然,写这个软件的人没有,只是从后台线程设置UI控件属性。当然,它抛出了通常的异常,但它被封装在一个所有捕获的try-catch块中。
我与一个利益相关者交谈,以了解这个功能被破坏了多长时间,结果证明它之前是好的。我简直不敢相信,但他向我演示了这个功能在PROD中是可以工作的。
所以我们不相关的更新在三个地方"破坏"了软件,但我无法想象它最初是如何工作的。根据源代码控制,这个bug一直存在。
当然,我们用适当的跨线程UI处理逻辑更新了应用程序,但现在我不得不不幸地向没有多少技术背景的利益相关者解释为什么一些不可能像以前一样工作的东西,直到我们对系统做了一些不相关的更改。
请赐教。
我还想到了一件事:在其中两种情况下,后台线程更新了一个ListView控件,而这个控件位于一个未选中的(因此也就没有显示)TabPage控件上。第三次是标准标签控件(设置了文本属性),最初为空,并根据后台线程找到的内容分配文本。
这里有一些代码,与我们发现的非常相似:
private void form_Load(object sender, System.EventArgs e)
{
// unrelated stuff...
ThreadStart ts = new ThreadStart(this.doWork);
Thread oThread = new Thread(ts);
ts.Start();
// more unrelated stuff ...
}
public void doWork()
{
string error = string.Empty;
int result = 0;
try
{
result = this.service.WhatsTheStatus(out error); // lengthy operation
switch (result)
{
case 1:
this.lblStatus.Text = "OK";
break;
case -1:
this.lblStatus.Text = "Error";
this.lblError.Text = error;
break;
default:
this.lblStatus.Text = "Unknown";
break;
}
}
catch
{
}
}
不幸的是,我看到lblStatus在生产环境中被更新了,而它在整个应用程序的其他任何地方都没有被引用(当然,除了设计器生成的东西)。
这是因为当您运行没有调试器的代码(在windows窗体中)时,不能保证会抛出跨线程访问异常。阅读以下示例:https://msdn.microsoft.com/en-us/library/vstudio/ms171728%28v=vs.110%29.aspx:
. net框架可以帮助您检测何时以非线程安全的方式访问控件。当您在调试器中运行应用程序时,如果创建控件的线程以外的线程试图调用该控件,则调试器将抛出InvalidOperationException,并发出消息"从创建该控件的线程以外的线程访问控件名称"
此异常在调试期间可靠地发生,在某些情况下,在运行时也会发生。在调试使用。net Framework 2.0之前的。net Framework编写的应用程序时,您可能会看到此异常。强烈建议您在看到此问题时修复此问题,但您可以通过将CheckForIllegalCrossThreadCalls属性设置为false来禁用它。这将使您的控件像在Visual Studio . net 2003和. net Framework 1.1下运行一样运行。
你可以自己写一个简单的winforms应用程序,从另一个线程设置表单标题,看看是否抛出异常。然后在不附加调试器的情况下运行相同的应用程序,看看表单标题如何愉快地改变而没有任何问题。