';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,并且运行良好(所以不是偶然选择了正确的线程)!

';RunWorkerCompleted';在错误的线程上执行

强烈提示您从错误的线程调用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();