BackgroundWorker 返回到错误的线程

发布于 2024-12-14 15:08:25 字数 1293 浏览 0 评论 0原文

在我的应用程序中,我使用以下代码创建一个新的 UI 线程:

Thread thread = new Thread(() =>
    {
        MyWindow windowInAnotherThread = new MyWindow();
        windowInAnotherThread.Show();
        System.Windows.Threading.Dispatcher.Run();
    }) { IsBackground = true };
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();

这给我带来了以下问题:

在 MyWindow 类的构造函数中,执行了一个 BackgroundWorker 。在 RunWorkerCompleted 中,应该使用后台工作程序正在计算的一些数据来更新控件。

我构建了一个小示例,它说明了这一点:

public partial class MyWindow : Window {
    public MyWindow() {
        InitializeComponent();

        var bw = new BackgroundWorker();
        bw.DoWork += bw_DoWork;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        bw.RunWorkerAsync();
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        this.Title = "Calculated title";
    }

    void bw_DoWork(object sender, DoWorkEventArgs e) {
        Thread.Sleep(3000);
    }
}

bw_RunWorkerCompleted() 中,我得到一个 InvalidOperationException (调用线程无法访问此对象,因为不同的线程拥有它。) 。看起来,BackgroundWorker 没有返回到它启动的正确 UI 线程。

有人可以帮助我,我能做些什么来解决这个问题吗?我无法更改正在执行BackgroundWorker 的代码,因为它位于我使用的框架中。但我可以在 RunWorkerCompleted-Event 中做其他事情。但我不知道如何解决这个问题。

In my application, I create a new UI-Thread with the fallowing code:

Thread thread = new Thread(() =>
    {
        MyWindow windowInAnotherThread = new MyWindow();
        windowInAnotherThread.Show();
        System.Windows.Threading.Dispatcher.Run();
    }) { IsBackground = true };
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();

This give me the fallowing problem:

In the constructor of the MyWindow class, a BackgroundWorker is executed. In the RunWorkerCompleted there should a Control be updated with some data, which the BackgroundWorker is calculating.

I have build a small sample, which is illustrating this:

public partial class MyWindow : Window {
    public MyWindow() {
        InitializeComponent();

        var bw = new BackgroundWorker();
        bw.DoWork += bw_DoWork;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        bw.RunWorkerAsync();
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        this.Title = "Calculated title";
    }

    void bw_DoWork(object sender, DoWorkEventArgs e) {
        Thread.Sleep(3000);
    }
}

In bw_RunWorkerCompleted() I get an InvalidOperationException (The calling thread cannot access this object because a different thread owns it.). It looks like, that the BackgroundWorker is not returning to the correct UI-Thread from which it was started from.

Can someone help me, what I can do to solve this problem? I can't change the Code which is executing the BackgroundWorker, because it is in a framework, which I use. But I can do something else in the RunWorkerCompleted-Event. But I have no idea, how to solve this problem.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

冷…雨湿花 2024-12-21 15:08:25

问题是窗口创建得太快。该线程还没有同步上下文。您可以通过在 BGW 构造函数调用上设置断点并查看 Thread.CurrentThread.ExecutionContext.SynchronizationContext 来看到这是调试器。它是空的。 BGW 使用它来决定如何封送 RunWorkerCompleted 事件。在没有同步上下文的情况下,事件在线程池线程上运行并调用愤怒。

您需要尽快初始化调度程序。这并不是 100% 正确的方法,但它似乎确实有效:

        Thread thread = new Thread(() => {
            System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => {
                MyWindow windowInAnotherThread = new MyWindow();
                windowInAnotherThread.Show();
            }));
            System.Windows.Threading.Dispatcher.Run();
        }) { IsBackground = true };
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

您还必须显式强制线程关闭。将此方法添加到 MyWindow:

    protected override void OnClosed(EventArgs e) {
        Dispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Background);
    }

The problem is that the window is getting created too soon. The thread doesn't have a synchronization context yet. You can see this is the debugger by setting a breakpoint on BGW constructor call and look at Thread.CurrentThread.ExecutionContext.SynchronizationContext. It's null. Which is what BGW uses to decide how to marshal the RunWorkerCompleted event. Which no synchronization context, the event runs on a threadpool thread and that invokes wrath.

You need to get the dispatcher initialized sooner. Not 100% this is the correct way but it did seem to work:

        Thread thread = new Thread(() => {
            System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => {
                MyWindow windowInAnotherThread = new MyWindow();
                windowInAnotherThread.Show();
            }));
            System.Windows.Threading.Dispatcher.Run();
        }) { IsBackground = true };
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

You also have to explicitly force the thread to shutdown. Add this method to MyWindow:

    protected override void OnClosed(EventArgs e) {
        Dispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Background);
    }
秋心╮凉 2024-12-21 15:08:25

遇到类似的问题。根据下面的注释 1 和注释 2,我创建了 UIBackgroundWorker。也许它可以帮助遇到此问题的其他开发人员。

如果它有效,请告诉我或更新设计以造福其他开发人员。

public class UIBackgroundWorker : BackgroundWorker
{

    private System.Windows.Threading.Dispatcher uiDispatcher;
    public SafeUIBackgroundWorker(System.Windows.Threading.Dispatcher uiDispatcher)
        : base()
    {
        if (uiDispatcher == null)
            throw new Exception("System.Windows.Threading.Dispatcher instance required while creating UIBackgroundWorker");
        else
            this.uiDispatcher = uiDispatcher;
    }

    protected override void OnProgressChanged(ProgressChangedEventArgs e)
    {
        if (uiDispatcher.CheckAccess())
            base.OnProgressChanged(e);
        else
            uiDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => base.OnProgressChanged(e)));
    }

    protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e)
    {
        if (uiDispatcher.CheckAccess())
            base.OnRunWorkerCompleted(e);
        else
            uiDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => base.OnRunWorkerCompleted(e)));
    }
}

Ran into similar issue. Based on note 1 and 2 below I created UIBackgroundWorker. May be it can help other developers who encounter this issue.

If it works then please let me know or update the design for benefit of other developers.

public class UIBackgroundWorker : BackgroundWorker
{

    private System.Windows.Threading.Dispatcher uiDispatcher;
    public SafeUIBackgroundWorker(System.Windows.Threading.Dispatcher uiDispatcher)
        : base()
    {
        if (uiDispatcher == null)
            throw new Exception("System.Windows.Threading.Dispatcher instance required while creating UIBackgroundWorker");
        else
            this.uiDispatcher = uiDispatcher;
    }

    protected override void OnProgressChanged(ProgressChangedEventArgs e)
    {
        if (uiDispatcher.CheckAccess())
            base.OnProgressChanged(e);
        else
            uiDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => base.OnProgressChanged(e)));
    }

    protected override void OnRunWorkerCompleted(RunWorkerCompletedEventArgs e)
    {
        if (uiDispatcher.CheckAccess())
            base.OnRunWorkerCompleted(e);
        else
            uiDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => base.OnRunWorkerCompleted(e)));
    }
}
眼角的笑意。 2024-12-21 15:08:25

问题是您需要设置 SynchronizationContext。这通常不是问题,因为 Dispatcher.Invoke 会为您设置它,但由于您在构造函数中使用 BackgroundWorker (在 >Dispatcher.Run),未设置上下文。

将线程创建更改为:

Thread thread = new Thread(() =>
    {
        // Create the current dispatcher (done via CurrentDispatcher)
        var dispatcher = Dispatcher.CurrentDispatcher;
        // Set the context
        SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(dispatcher));

        MyWindow windowInAnotherThread = new MyWindow();
        windowInAnotherThread.Show();
        Dispatcher.Run();
    });

thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();

这将使其正确运行,因为 SynchronizationContext 将在构建窗口之前就位。

The problem is that you need to setup the SynchronizationContext. This is normally not an issue, as Dispatcher.Invoke will set it up for you, but since you're using BackgroundWorker in the constructor (which is fired prior to Dispatcher.Run), no context is setup.

Change your thread creation to:

Thread thread = new Thread(() =>
    {
        // Create the current dispatcher (done via CurrentDispatcher)
        var dispatcher = Dispatcher.CurrentDispatcher;
        // Set the context
        SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(dispatcher));

        MyWindow windowInAnotherThread = new MyWindow();
        windowInAnotherThread.Show();
        Dispatcher.Run();
    });

thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();

This will cause it to run correctly, as the SynchronizationContext will be in place prior to the construction of the Window.

╰つ倒转 2024-12-21 15:08:25

尝试为 MyWindow 内的 BackgroundWorker 提供 gettersetter。并通过setter方法将BackgroundWorker对象传递给Mywindow。我想这应该可以解决问题。

Try providing a getter and setter for your BackgroundWorker inside MyWindow. And pass BackgroundWorker object via setter method to Mywindow. That should solve the problem, i guess.

孤凫 2024-12-21 15:08:25

您需要在调用函数中使用委托方法和调用。这里有一个很好的例子:http://msdn。 microsoft.com/en-us/library/aa288459(v=vs.71).aspx

使用您的代码,

    public partial class MyWindow : Window {


    delegate void TitleSetter(string title);

    public MyWindow() {
            InitializeComponent();

        var bw = new BackgroundWorker();
        bw.DoWork += bw_DoWork;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        bw.RunWorkerAsync();
    }

    void SetTitle(string T)
    {
      this.Title = T;
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {

      try    
        {
        TitleSetter T = new TitleSetter(SetTitle);
        invoke(T, new object[]{"Whatever the title should be"}); //This can fail horribly, need the try/catch logic.
        }catch (Exception){}
    }

    void bw_DoWork(object sender, DoWorkEventArgs e) {
        Thread.Sleep(3000);
    }
}

You need to use a delegate method and an invoke in the calling function. There's a good example here: http://msdn.microsoft.com/en-us/library/aa288459(v=vs.71).aspx

Using your code,

    public partial class MyWindow : Window {


    delegate void TitleSetter(string title);

    public MyWindow() {
            InitializeComponent();

        var bw = new BackgroundWorker();
        bw.DoWork += bw_DoWork;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        bw.RunWorkerAsync();
    }

    void SetTitle(string T)
    {
      this.Title = T;
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {

      try    
        {
        TitleSetter T = new TitleSetter(SetTitle);
        invoke(T, new object[]{"Whatever the title should be"}); //This can fail horribly, need the try/catch logic.
        }catch (Exception){}
    }

    void bw_DoWork(object sender, DoWorkEventArgs e) {
        Thread.Sleep(3000);
    }
}
怎樣才叫好 2024-12-21 15:08:25

我认为只需将后台工作线程设置代码移至“Load”事件而不是构造函数中就可以了。

I think simply moving your background worker thread setup code into the "Load" event instead of the constructor should be just fine.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文