2

http://msdn.microsoft.com/en-us/magazine/gg598924.aspx

Why exceptions are not propagated by WPF Dispatcher.Invoke?

How can I allow Task exceptions to propagate back to the UI thread?

In the code below I need to propagate execeptions that are thrown in the tasks and their continuations back up to the ui thread where they will be handled by LogException. If I need to re-throw an exception somewhere along the line thats fine with me. Whatever works. How do I do that? I referenced some questions that are similar to mine but I do not see an answer that is relevant to my app.
Edit 3: posted a simplified example

Edit 2: See this: http://msdn.microsoft.com/en-us/library/dd997415(v=vs.100).aspx

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += new RoutedEventHandler(MainWindow_Loaded);
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        FireAndForget();
        WaitOnTask();
    }

    private void FireAndForget()
    {
        Task t1 = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(3000);
            throw new Exception("boo"); 
        });

        Task c1 = t1.ContinueWith((t) =>
            {
                // The app global exception handler will not catch this.

            }, TaskContinuationOptions.OnlyOnFaulted);

        //MessageBox.Show("Task is running");
    }


    private void WaitOnTask()
    {
        Task t1 = Task.Factory.StartNew(() =>
        {
            throw new Exception("boo");
        });

        try
        {
            t1.Wait();
        }
        catch (Exception ex)
        {
            // The app global exception handler will catch this:
            throw new Exception("Task", ex);
        }
    }
}



public partial class App : Application
{
    public App()
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        Application.Current.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(Current_DispatcherUnhandledException);
        //System.Threading.Tasks.TaskScheduler.UnobservedTaskException += new EventHandler<System.Threading.Tasks.UnobservedTaskExceptionEventArgs>(TaskScheduler_UnobservedTaskException);
    }

    void TaskScheduler_UnobservedTaskException(object sender, System.Threading.Tasks.UnobservedTaskExceptionEventArgs e)
    {
        LogException(e.Exception);
    }

    void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
    {
        LogException(e.Exception);
    }

    void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        LogException(e.ExceptionObject as Exception);
    }

    private void LogException(Exception ex)
    {
        // log it
        string error = "This app has encountered an unexpected error .  The error message is:" + Environment.NewLine + ex.Message + Environment.NewLine;
        Exception tmp = ex.InnerException;

        while (tmp != null)
        {
            error += "Inner exception is: " + Environment.NewLine + tmp.Message + Environment.NewLine;
            tmp = tmp.InnerException;
        }

        error += "Please press OK to exit.";
        MessageBox.Show(error, "Error");
        Environment.Exit(-1);
    }
}


  • Is there any reason you're not using async/await instead of ContinueWith? - Stephen Cleary
  • @StephenCleary yes this is .net 4 app - Sam
  • Have you considered Microsoft.Bcl.Async? - Stephen Cleary
  • @StephenCleary Stephen, thank you for your suggestion - my app is pretty complex I would really want to do that as a last resort. Is there a more direct way to accomplish what I asked? - Sam
  • It seems to me that async would really simplify your app, but I'll post an answer in a few minutes that should work. - Stephen Cleary

5 답변


3

When you use StartNew or ContinueWith, any exceptions are placed on the returned Task.

There are two problems with marshaling exceptions:

  1. Task.Exception wraps your exception in an AggregateException.
  2. When you throw an exception later (e.g., on another thread), the original call stack is lost.

For the first problem, some people use the Flatten or Handle members to work directly with AggregateException. I prefer unwrapping the exceptions by dealing with Task.Exception.InnerException instead of Task.Exception.

For the second problem, some people work around it by wrapping it in another exception, but I have taken an alternative approach. .NET 4.5 introduced ExceptionDispatchInfo, which is the correct way to do this. In .NET 4.0 you can hack something like this:

public static Exception Rethrow(this Exception ex)
{
  typeof(Exception).GetMethod("PrepForRemoting",
      BindingFlags.NonPublic | BindingFlags.Instance)
      .Invoke(ex, new object[0]);
  throw ex;
}


  • Stephen, I implemented your method and modified my code: t.Exception.Rethrow() but it does not work. - Sam
  • What exactly do you mean "it does not work"? - Stephen Cleary
  • My exception handler is not called - Sam
  • I'm not getting a clear picture of what you're saying. Please update your question with a minimal repro. - Stephen Cleary
  • Changes posted. - Sam

1

I'm not sure if i'm missing something here, but if you use TaskScheduler.FromCurrentSynchronizationContext() as the second parameter to ContinueWith then it will be marshaled back onto your UX thread.

I actually wrote a blog post about it if you want a little more of a sample. http://www.briankeating.net/post/Why-I-love-the-Task-library

Kr, Brian.


  • It's true that code will be marshalled on interface thread, but all exceptions thrown in continuation will be handled differently - they will be reported using TaskScheduler.UnobservedTaskException instead of Dispatcher.UnhandledException - ghord

0

The answer to the question is found here: http://blogs.msdn.com/b/pfxteam/archive/2009/05/31/9674669.aspx

Basically there are two scenarios: Situations where you can wait on the task and situations where you cannot i.e. fire and forget. In situations where you can wait on the task, wrap it in a try block as shown in the question and rethrow the error. The global app handler will catch it.

In situtions where you cannot wait on the task you have to call your logger manually. There is no application level handler that will catch the error. There is a possibility that TaskScheduler.UnobservedTaskException will fire, however that event is IMHO highly circumstantial and fragile and not a good option.


0

To propagate the exceptions in your code you need to Wait on all the tasks. If you make the following changes to your FireAndForget method the Exception in the nested Task will be propagated back to the calling thread.

    private void FireAndForget()
    {
        var tasks = new Task[2];
        tasks[0] = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(3000);
            throw new Exception("boo");
        });

        tasks[1] = tasks[0].ContinueWith((t) =>
        {
            throw new Exception("nested boo", tasks[0].Exception);

        }, TaskContinuationOptions.OnlyOnFaulted);

        try
        {
            Task.WaitAll(tasks);
        }
        catch (AggregateException ex)
        {
            throw new Exception("Task", ex);
        }
    }

Of course this is no longer a "fire and forget" method. If waiting on the tasks is undesirable you will need to write to your log file from within the continuation.


  • Right, or add a try/catch block inside of task[0] - Sam

-1

You can await the completion of the task to receive exception from the task code.

try{
  await Task.Factory.StartNew(() => throw Exception("hello"));       
}catch{
  // will get exception here
}


  • I cant await I am using .net 4. However I tried using WaitAll in the example I provided and the app simply hangs. - Sam

Linked


Related

Latest