3

在我的应用程序中,我使用以下代码创建了一个新的 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 中做其他事情。但我不知道如何解决这个问题。

4

6 回答 6

4

问题是窗口创建得太快了。该线程还没有同步上下文。您可以通过在 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);
    }
于 2011-11-08T22:35:40.640 回答
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)));
    }
}
于 2012-09-18T06:22:12.977 回答
1

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

于 2011-11-08T22:48:48.070 回答
0

尝试为您的内部提供一个getterand 。并通过 setter 方法将 BackgroundWorker 对象传递给 Mywindow。这应该可以解决问题,我猜。setterBackgroundWorkerMyWindow

于 2011-11-08T22:18:55.407 回答
0

您需要在调用函数中使用委托方法和调用。这里有一个很好的例子: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);
    }
}
于 2011-11-08T22:19:05.987 回答
0

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

于 2011-11-08T22:43:43.000 回答