我有一个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并且一致地运行良好(因此不仅仅是偶然选择了正确的线程)!
答案 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();