';RunWorkerCompleted';在错误的线程上执行
本文关键字:执行 线程 RunWorkerCompleted 错误 | 更新日期: 2023-09-27 18:20:01
我有一个winforms应用程序,在主Form的Application.Run
之前运行BackgroundWorker。
当BackgroundWorker完成时,在其RunWorkerCompleted
处理程序中,它访问主Form,我得到异常:
"跨线程操作无效:从创建线程之外的线程。"
所以我认为这个错误与这个评论有关,该评论指出RunWorkerCompleted
"只有当UI线程创建BGW实例。"
(尽管它看起来不像是在单独的线程上创建的)。(也可以看到这里的评论)。
所以我创建了一个简单的测试,在Application.Run
之前先BW.RunWorkerAsync();
(在"程序"中),它在那里工作得很好。未引发异常。
那么问题出在哪里呢?为什么当我从同一个线程运行BackgroundWorker时,与主窗体的交互会引发异常?
(我不能在这里发布整个代码,因为它很长。只发布相关代码是我之前提到的-它确实而不是抛出异常。)
编辑
因此,也许还有更具体的问题:如何让"UI线程创建BGW"?它必须在应用程序内部运行吗?显示表格后?它是否可能不取决于是哪个线程创建了BGW,而是取决于哪个线程调用RunWorkerSync?
编辑2
检查Thread.CurrentThread.ManagedThreadId
时,我发现它在RunWorkerAsync
之前8(就像在DoWork +=
和RunWorkerCompleted +=
之前一样),但在RunWorkerCompleted
处理程序内部9。
当遍历代码并在RunWorkerAsync()
之后等待几秒钟时,它们都有相同的线程ID,并且运行良好(所以不是偶然选择了正确的线程)!
强烈提示您从错误的线程调用RunWorkersync()。BGW需要弄清楚它在哪个特定线程上运行事件。它自己无法做到这一点,它需要帮助。您可以简单地在程序中添加一些诊断代码来验证是否提供了此帮助:
public static class DebugUtils {
public static void CheckThreadState() {
if (System.Threading.SynchronizationContext.Current == null) {
throw new InvalidOperationException("You are on the wrong thread")
}
}
}
并在代码中调用RunWorkersync()的所有位置插入此调用:
DebugUtils.CheckThreadState();
下面是一个有效的例子:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Form1 form = null;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
BackgroundWorker workerThread = new BackgroundWorker();
workerThread.DoWork += delegate
{
Thread.Sleep(1500);
};
workerThread.RunWorkerCompleted += delegate
{
if ( form != null )
form.BackColor = Color.Red;
};
workerThread.RunWorkerAsync();
form = new Form1();
Application.Run(form);
}
}
}
好的,它有一些种族问题。但我看不出有什么问题。Run和主线程是同一个UI线程,您可以在调试中检查它。如何破例?
BGW不能只是神奇地在UI线程中运行代码。它需要有一些机制来知道UI线程是什么,以便在那里封送事件处理程序。
它实际使用的是SynchronizationContext.Current
。当您调用RunWorkerAsync
,然后使用该上下文封送事件处理程序时,它会"记住"当前上下文是什么。正是对Application.Run
的调用为UI的消息循环创建了同步上下文,因此,由于您甚至在拥有消息循环或同步上下文之前就启动了worker,因此它无法在那里封送代码。
您需要等待启动后台工作程序,直到您真正拥有消息循环。一种简单的方法是在表单中的一个事件中启动它,该事件在设置消息循环之前不会被触发,例如Load
事件:
form.Load += (s, args) => worker.RunWorkerAsync();