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

时间:2014-08-29 10:17:40

标签: c# .net multithreading winforms backgroundworker

我有一个winforms应用程序,我在主窗体的Application.Run之前运行 BackgroundWorker

BackgroundWorker 完成时,在其RunWorkerCompleted处理程序中 - 它访问主窗体,我得到例外:

  

"跨线程操作无效:控制' Form1'从a访问   除了创建它的线程以外的线程。"

所以我认为该错误与this comment有关,RunWorkerCompleted

  

"只有在UI线程创建时才会在UI线程上引发   BGW实例。"

(虽然它似乎似乎就像它在一个单独的线程上创建的那样)。 (另见this comment there)。

所以我在BW.RunWorkerAsync();之前创建了一个简单的测试Application.Run(在" 程序")并且在那里工作正常。没有例外。

那么可能是什么问题?虽然我从同一个线程运行 BackgroundWorker ,为什么与主表单进行交互会抛出异常?

(我不能在这里发布整个代码,因为它很长。而且只发布了我之前提到的相关代码 - 它确实抛出一个例外。)

修改

所以也许可能会有更具体的问题:如何让" UI线程创建BGW"?它必须在Application.Run中吗?表格显示后?是否依赖于哪个线程创建 BGW,但哪个线程调用RunWorkerAsync?

编辑2

检查Thread.CurrentThread.ManagedThreadId我在RunWorkerAsync之前看到它 8 (因为它在DoWork +=RunWorkerCompleted +=之前)但是RunWorkerCompleted 处理程序内的 9

当单步执行代码并在RunWorkerAsync()之后等待几秒钟时 - 它们都具有相同的线程ID并且一致地运行良好(因此不仅仅是偶然选择了正确的线程)!

3 个答案:

答案 0 :(得分:2)

强烈暗示您从错误的线程调用RunWorkerAsync()。 BGW需要弄清楚它运行事件的特定线程。它本身不能做到这一点,它需要帮助。您只需在程序中添加一些诊断代码即可验证是否提供了此帮助:

public static class DebugUtils {
    public static void CheckThreadState() {
        if (System.Threading.SynchronizationContext.Current == null) {
            throw new InvalidOperationException("You are on the wrong thread")
        }
    }
}

并在您调用RunWorkerAsync()的代码的所有位置插入此调用:

DebugUtils.CheckThreadState();

答案 1 :(得分:0)

这是一个有效的例子:

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);
        }

    }
}

好吧,它有一些种族问题。但我没有看到任何问题。运行和主线程是相同的UI线程 - 您可以在调试中检查它。 如何例外?

答案 2 :(得分:0)

BGW不能只是在UI线程中神奇地运行代码。它需要有一些机制,通过它可以知道UI线程是什么,以便它可以在那里封送事件处理程序。

它实际使用的是SynchronizationContext.Current。当你调用RunWorkerAsync然后使用该上下文来编组事件处理程序时,它“记住”当前上下文是什么。调用Application.Run正在为UI的消息循环创建同步上下文,因此,在您拥有消息循环或同步上下文之前启动工作程序时,它无法在那里编组代码

您需要等待启动后台工作程序,直到您实际拥有消息循环。一种简单的方法是在表单的事件中启动它,直到设置了消息循环后才会触发,例如Load事件:

form.Load += (s, args) => worker.RunWorkerAsync();