【问题标题】:BackgroundWorker returns to wrong threadBackgroundWorker 返回错误的线程
【发布时间】:2011-11-08 22:13:20
【问题描述】:

在我的应用程序中,我使用以下代码创建了一个新的 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 中,应该使用 BackgroundWorker 正在计算的一些数据更新 Control。

我已经建立了一个小样本,它说明了这一点:

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 中做其他事情。但我不知道如何解决这个问题。

【问题讨论】:

  • 你为什么要“创建一个新的 UI 线程”呢?没有必要,没有生产力,也是这个问题的根源。

标签: c# wpf multithreading backgroundworker ui-thread


【解决方案1】:

问题是窗口创建得太快了。该线程还没有同步上下文。您可以通过在 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);
    }

【讨论】:

  • 或者延迟创建bgw,现在我想到了:)
  • 你不能像这样调用 BeginInvoke,因为它会将 Window 创建推送到 UI 线程中(Dispatcher.BeginInvoke 是当前窗口的调度器,而不是新线程的调度器......)跨度>
  • 我认为如果你使用Dispatcher.CurrentDispatcher.BeginInvoke(...)这会有效
  • 是的,这就是我所担心的。最好删除它。
  • 我开始写这个 - 然后想我应该测试它,这就是我想出我的版本的方式......刚刚试过,如果你使用Dispatcher.CurrentDispatcher.BeginInvoke,这种方法有效。
【解决方案2】:

遇到了类似的问题。根据下面的注释 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)));
    }
}

【讨论】:

    【解决方案3】:

    问题是您需要设置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 将在构建窗口之前就位。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        您需要在调用函数中使用委托方法和调用。这里有一个很好的例子: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);
            }
        }
        

        【讨论】:

        • 这应该没有必要。在 WPF 中是 Dispatcher.Invoke()
        【解决方案6】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多