0

In my application (.NET 4.0) I use smartassembly for error reporting with a custom template. It installs two handlers:

  1. It installs a global exception catcher, and calls my custom code if an exception occurs. There I display a WPF window which shows the details of the exception and allows the user to send the data via the internet.
  2. If an exception occurs which cannot be handled by #1, it calls a fatal exception handler. There I output the exception data in a message box.

On one customer's machine (Windows XP, .NET 4.0) he gets an error message from #2 after the application starts. Then the application is terminated:

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)

The relevant code:

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
    }
}

So the following happens:

  1. During startup an exception occurs (unfortunately, this gets lost).
  2. To handle the exception, smartassembly calls the handler #1, OnReportException().
  3. There I create a new ErrorReportView.
  4. WPF throws a cross-thread exception in the constructor (before InitializeComponent())!
  5. Because an exception occurred while handling the exception, smartassembly calls the handler #2 and terminates the application.

How is it possible that a simple new Window() can cause a cross-thread exception on itself?

4
  • I have no knowledge about smartAssembly but it seems that the OnReportException method is executed in a thread that is not the UI tread. Did you try to create the ErrorReportView through Application.Current.Dispatcher.Invoke method ? EDIT: Here is an answer worth trying. Commented Nov 20, 2012 at 12:24
  • Don't get too fancy in an event handler for AppDomain.UnhandledException. It is raised on the thread that suffered the crash so creating a WPF window isn't going to work. Keep it simple, log the error and squeeze out a MessageBox, no more. Commented Nov 20, 2012 at 13:14
  • @HansPassant I have to be somewhat fancy, since the data is sent via the internet and I need consent from the user. Commented Nov 20, 2012 at 13:19
  • You are well past needing any kind of consent. Your program crashed, the show is over. Commented Nov 20, 2012 at 13:49

3 Answers 3

2

Try to create your ErrorReportView using the 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();
        }));
    }
}

As I can't test it or reproduce your issue, I'm not sure it will work, but it's worth a try.

Sign up to request clarification or add additional context in comments.

5 Comments

It works, but it isn't exactly a very safe thing to do. It can easily deadlock if the exception was thrown by code that ran on the UI thread.
Thanks for the input. I guess it should be possible to detect the thread from wich the exception was thrown and adapt consequently by creating the window directly or through the dispatcher. I have not given this issue a lot of though though and won't go any further ;)
That got me thinking. I unfortunately missed some code, namely where I set the Owner of the window. If I create an exception from a different, STA thread, I get a similar exception (not quite, as the exception detail points to a different position, namely at the Application.Current.MainWindow call).
If I have a release build, the stack trace changes (perhaps due to optimizations?) to the stack trace I received.
I'm really not sure, it's hard to answer this kind of issue without actual code ^^
1

An option is to fire a dedicated thread to handle this report. It would be something like this:

[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();
}

Note that:

  • The window must be created and shown inside the new thread.
  • You must initiate a dispatcher (System.Windows.Threading.Dispatcher.Run()) before the ThreadStart returns, otherwise the window will show and die soon after.
  • The thread must be configured to run in STA apartment.

You can find additional information in this link.

Comments

0

With Arthur Nunes's answer and Sisyphe's answer I now handle all possibilities. The exception is apparently being thrown on an STA thread, but that thread is not the main (UI) thread. Probably due to JIT optimizations, the stack trace I got is a little incomplete and shows the exception happening at the wrong place.

The fixed code:

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;
        }
    }
}

1 Comment

Gratz on solving your issue mate !

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.