创建WPF窗口时出现跨线程异常

时间:2012-11-20 12:09:27

标签: wpf .net-4.0

在我的应用程序(.NET 4.0)中,我使用smartassembly进行自定义模板的错误报告。它安装了两个处理程序:

  1. 它安装了一个全局异常捕获器,并在发生异常时调用我的自定义代码。在那里,我显示一个WPF窗口,显示异常的详细信息,并允许用户通过互联网发送数据。
  2. 如果发生#1无法处理的异常,则会调用致命异常处理程序。我在消息框中输出异常数据。
  3. 在一台客户的计算机(Windows XP,.NET 4.0)上,他在应用程序启动后收到来自#2的错误消息。然后申请被终止:

    System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
      at System.Windows.Threading.Dispatcher.VerifyAccess()
      at Exapt.ErrorReporting.ErrorReportView..ctor()
      at Exapt.ErrorReporting.ExaptUnhandledExceptionHandler.OnReportException(ReportExceptionEventArgs e)
      at SmartAssembly.SmartExceptionsCore.UnhandledExceptionHandler.ReportException(Exception exception, Boolean canContinue, Boolean manuallyReported)
    

    相关代码:

    public ExaptUnhandledExceptionHandler : UnhandledExceptionHandler
    {
        protected override void OnReportException(ReportExceptionEventArgs e)
        {
            var view = new ErrorReportView();
            view.DataContext = new ErrorReportViewModel(this, e, view);
    
            view.ShowDialog();
        }
    }
    
    public ErrorReportView : Window
    {
        public ErrorReportView()
        {
            this.InitializeComponent();
    
            // EDIT
            if (Application.Current != null)
                this.Owner = Application.Current.MainWindow;
            // END EDIT
        }
    }
    

    所以发生以下情况:

    1. 在启动期间发生异常(不幸的是,这会丢失)。
    2. 要处理异常,smartassembly会调用处理程序#1,OnReportException()。
    3. 我在那里创建了一个新的ErrorReportView。
    4. WPF在构造函数中抛出一个跨线程异常(在InitializeComponent()之前)!
    5. 由于处理异常时发生异常,因此smartassembly会调用处理程序#2并终止该应用程序。
    6. 一个简单的新Window()如何可能导致自身的跨线程异常?

3 个答案:

答案 0 :(得分:2)

尝试使用WPF ErrorReportView创建Dispatcher

public ExaptUnhandledExceptionHandler : UnhandledExceptionHandler
{
    protected override void OnReportException(ReportExceptionEventArgs e)
    {
        Application.Current.Dispatcher.Invoke(new Action(() => 
        {
            var view = new ErrorReportView();
            view.DataContext = new ErrorReportViewModel(this, e, view);
            view.ShowDialog();
        }));
    }
}

由于我无法测试或重现您的问题,我不确定它是否有效,但值得一试。

答案 1 :(得分:1)

选项是触发专用线程来处理此报告。它会是这样的:

[TestMethod]
public void TestMethod1()
{
    MainWindow window = null;

    // The dispatcher thread
    var t = new Thread(() =>
    {
        window = new MainWindow();

        // Initiates the dispatcher thread shutdown when the window closes
        window.Closed += (s, e) => window.Dispatcher.InvokeShutdown();

        window.Show();

        // Makes the thread support message pumping
        System.Windows.Threading.Dispatcher.Run();
    });

    // Configure the thread
    t.SetApartmentState(ApartmentState.STA);
    t.Start();
    t.Join();
}

请注意:

  • 必须创建窗口并在新线程中显示。
  • 您必须在ThreadStart返回之前启动调度程序(System.Windows.Threading.Dispatcher.Run()),否则窗口将很快显示并死亡。
  • 必须将线程配置为在STA公寓中运行。

您可以在this link

中找到更多信息

答案 2 :(得分:0)

通过Arthur Nunes的回答和Sisyphe的回答,我现在处理所有可能性。显然是在STA线程上抛出异常,但该线程不是主(UI)线程。可能由于JIT优化,我得到的堆栈跟踪有点不完整,并显示异常发生在错误的位置。

固定代码:

public ExaptUnhandledExceptionHandler : UnhandledExceptionHandler
{
    protected override void OnReportException(ReportExceptionEventArgs e)
    {
        // Create a new STA thread if the current thread is not STA.
        if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
        {
            this.ShowErrorReportView(e);
        }
        else
        {
            // Since I use ShowDialog() below, there is no need for Dispatcher.Run()
            // or Dispatcher.InvokeShutdown()
            var thread = new Thread(() => this.ShowErrorReportView(e));
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
            thread.Join();
        }
    }

    private void ShowErrorReportView(ReportExceptionEventArgs e)
    {
        var view = new ErrorReportView();
        view.DataContext = new ErrorReportViewModel(this, e, view);

        view.ShowDialog();
    }
}

public ErrorReportView : Window
{
    public ErrorReportView()
    {
        this.InitializeComponent();

        // All of these cause accessing the MainWindow property or setting the Owner
        // to throw an exception.
        if (Application.Current != null
            && Application.Current.Dispatcher.CheckAccess()
            && Application.Current.MainWindow != null
            && Application.Current.MainWindow != this
            && Application.Current.MainWindow.IsLoaded)
        {
            this.Owner = Application.Current.MainWindow;
        }
    }
}
相关问题