为啥我没有收到“跨线程操作无效”错误

Posted

技术标签:

【中文标题】为啥我没有收到“跨线程操作无效”错误【英文标题】:Why do I not get the "Cross-thread operation not valid" error为什么我没有收到“跨线程操作无效”错误 【发布时间】:2012-04-03 06:31:15 【问题描述】:

我使用 BackgroundWorker 并这样做:

private void loadNewAsyncToolStripMenuItem_Click(object sender, EventArgs e)

    this.Text = "RunWorkerAsync()";
    backgroundWorkerLoading.RunWorkerAsync();

private void backgroundWorkerLoading_DoWork(object sender, DoWorkEventArgs e)

    UnsafeThreadMethod("hello");
    EvenUnsaferThreadMethod();

现在是两种方法。

private void UnsafeThreadMethod(string text)

    toolStripLabelRssFeedData.Text = text;


private void EvenUnsaferThreadMethod()

    panelLoading.Visible = true;

我不明白为什么 UnsafeThreadMethod 不会抛出以下异常,而 EvenUnsaferThreadMethod 会。

跨线程操作无效:控件“panelLoading”从创建它的线程以外的线程访问。

根据消息,这是因为toolStripLabelRssFeedData 是在同一个线程上创建的,但事实并非如此。

我以为我不能调用主线程创建的控件,必须使用ProgressChanged 事件。怎么回事?

我还有第二个问题。当我可以使用ProgressChanged 时,这样做有什么好处?我该怎么办?

private void EvenUnsaferThreadMethod()

    if (panelLoading.InvokeRequired)
    
        panelLoading.Invoke(new MethodInvoker(() =>  EvenUnsaferThreadMethod(); ));
    
    else
    
        panelLoading.Visible = true;
    

【问题讨论】:

【参考方案1】:

第一个问题:

在调试模式下故意抛出跨线程异常。这意味着在大多数 GUI 控件中内置了对 InvokeRequired 的(条件)代码检查。就像面板一样。

显然,ToolstripLabel 没有进行此项检查。由于它不是从 Control 派生的,因此可能是因为它超出了此安全网的范围。

由于标准免责声明“不保证任何实例成员都是线程安全的”适用于 ToolstripLabel,因此在设置 Text 时,我将使用正常的 InvokeRequired 逻辑。

【讨论】:

我认为这个答案解决了 OP 的问题,这就是为什么一个看起来很不安全的调用没有引发异常的原因。这一切都围绕着这样一个事实,即 ToolStripLabel 不是从 Control 派生的,因此不检查线程安全。它只是碰巧有效,但正如其他人指出的那样,你不能指望它永远保持真实。【参考方案2】:

对于你的第一个问题,我不太确定,但是网上的评论似乎表明,有时这不会抛出异常,但不会更新标签。这里是这样吗?您的标签是否正在更新,没有例外?

不过,我现在可以回答你第二个问题。 ProgressChanged 事件的含义正是它听起来的样子。它应该被调用来让 UI 线程知道 backgroundworker 的状态,以便它可以适当地更新自己。原始调用线程(本例中为 UI)是用于ProgressChanged 的线程,因此当它更新时不需要调用Invoke。但是,这实际上应该只用于显示后台工作人员的进度。

现在,如果您尝试传递给调用方法的不是更新,那么我建议您通过 RunWorkerCompleted 事件将返回数据传回。这会将您的所有最终数据传回原始 (UI) 线程,以便它可以更新 UI 而无需 Invoke

所以,是的,您拨打Invoke 的电话会起作用。但是,了解其他每个事件的用途可以帮助您了解为什么要使用一种方式而不是另一种方式。也许ProgressChanged 事件更合适?它还可以使您的代码避免不必要的调用。

更新到第一个 q

我仍然找不到关于不需要调用的工具条的任何信息。事实上,我使用谷歌搜索找到了相反的结果,如“toolstriplabel no cross thread exception”或“toolstriplabel invoke”等。然而,正如henk所提到的,toolstriplabel不会从控件继承,所以这可以解释为什么不需要调用。但是,我的建议是假设它会像任何其他 UI 控件一样工作,并确保它在 UI 线程上更新以确保安全。 不要依赖怪癖。安全总比后悔好,你永远不知道这样的事情是否会改变,特别是因为它在逻辑上对大多数人来说是一个 UI 项目..,

【讨论】:

感谢您的回答。关于第一个问题:是的,它会更新标签就好了。您对我的第二个问题的回答使事情清楚了很多。我认为在我的情况下RunWorkerCompleted 是要走的路。 很高兴为您提供帮助。在进一步研究后,我更新了我的答案以给出我对标签的看法【参考方案3】:

second 选择的优势在于它有效 :)

所有 UI 元素都是在主 UI 线程上创建的,从这个问题的角度来看,更重要的是,只能在该线程内访问

这就是您的 first 案例失败的原因,这也是您的 second 案例有效的原因。 Invoke()... 会将所需的 merhod 调用重定向到主 UI 线程。

希望这会有所帮助。

【讨论】:

感谢您的回答。你说UnsafeThreadMethod 不应该工作,因为 UI 的东西在不同的线程上,但不知何故它确实如此,这就是我不明白的。另外,最后一个问题是使用 Invoke 的东西还是 ProgressChangedRunWorkerCompleted 更好(因为它们又回到了正确的线程上)。 第一个案例 sometimes 对我来说听起来很奇怪。第二种情况呢:考虑到在您的方法中访问 UI 元素,您必须使用Invoke 以便将调用重定向到 UI 线程。它不能以其他方式工作。 不知何故,因为行为不确定。它可以随意破坏,在服务包中,在修补程序中。 直到现在我还没有遇到过有效的案例,但即使有,也绝对是不是可以转发的。始终esplicitly 重定向到 UI 线程。除非它没有内置到框架本身来确定跨线程调用并自动重定向到 UI 线程。

以上是关于为啥我没有收到“跨线程操作无效”错误的主要内容,如果未能解决你的问题,请参考以下文章

不一致的“跨线程操作无效”异常

跨线程操作无效:控件从创建它的线程以外的线程访问

跨线程操作无效:控件“chart1”从创建它的线程以外的线程访问

获取跨线程操作无效[重复]

侦听 COM 端口时跨线程操作无效[重复]

例外:跨线程操作无效:控制'pgImportProcess(进度条)'从一个线程访问,而不是它在[重复]上创建的线程