0

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

  1. 它安装了一个全局异常捕获器,并在发生异常时调用我的自定义代码。在那里我显示了一个 WPF 窗口,其中显示了异常的详细信息并允许用户通过 Internet 发送数据。
  2. 如果发生 #1 无法处理的异常,它会调用致命异常处理程序。我在消息框中输出异常数据。

在一个客户的机器(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 并终止应用程序。

一个简单的 new Window() 怎么可能导致自身发生跨线程异常?

4

3 回答 3

2

尝试ErrorReportView使用 WPF 创建您的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();
        }));
    }
}

由于我无法对其进行测试或重现您的问题,因此我不确定它是否会起作用,但值得一试。

于 2012-11-20T12:28:38.973 回答
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 单元中运行。

您可以在此链接中找到更多信息。

于 2012-11-20T13:42:20.023 回答
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;
        }
    }
}
于 2012-11-20T14:29:40.830 回答