locked
How to rethrow an exception inside an async method n a WinRT component

    Question

  • I have created a C++ WinRT component to be consumed by a C# client. I am having some difficulty throwing exceptions from within a task continuation inside an async method, so I created a simple test case to show the problem.

    The method looks like this:

    // Class1.cpp
    #include "pch.h"
    #include "Class1.h"
    #include <ppltasks.h>
     
    using namespace WinRTTestComponent;
    using namespace Platform;
    using namespace concurrency;
    using namespace Windows::Storage;
     
    IAsyncActionClass1::TestAsync(UriuriIn)
    {
      return create_async([this, uriIn]()
      {
    	  auto task1 = create_task(StorageFile::GetFileFromApplicationUriAsync(uriIn));
     
    	  // Task-based continuation
    	  auto continuation1 = task1.then([this, uriIn] (task<StorageFile^> task1FileOutput)
    	  {
    		try
    		{
    			// Get the output of the first task = will throw a FileNotFoundException from
    			// GetFileFromApplicationUriAsync if target Uri does not exist
    			auto somedata = task1FileOutput.get()->Path->Data();
     
    		}
    		catch (Platform::Exception^ e)
    		{
    			// Just test rethrowing the exception
    			throw;
    			
    			// What I really want to do is something like this...
    			//if (e->HResult != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
    			//{
    			//	throw;	//rethrow
    			//}
    			//else
    			//{
    			//	// ... do something...
    			//}
    		}
    	});
      });
    }

    When I call it from C# passing the Uri of a file that does not exist, instead of the WinRT component throwing a FileNotFoundException as I would expect, the app crashes with an unhandled Win32 exception.

    This problem is not just about the correct way to rethrow an exception from the exception handler - it's throwing *any* exception from within a continuation. For example, if I simplify the method to the following, but this time call it from my C# app passing the Uri of a file that does exist (so that GetFileFromApplicationUriAsync doesn't throw, but the code in the continuation does throw an exception), I get the same outcome - an unhandles Win32 exception crashes my app:

    IAsyncActionClass1::TestAsync(UriuriIn)
    {
      return create_async([this, uriIn]()
      {
    	  auto task1 = create_task(StorageFile::GetFileFromApplicationUriAsync(uriIn));
     
    	  // Task-based continuation
    	  auto continuation1 = task1.then([this, uriIn] (task<StorageFile^> task1FileOutput)
    	  {
    		  auto somedata = task1FileOutput.get()->Path->Data();
    		  throw ref new FailureException("Something went wrong");
    	  });
      });
    }

    What is the correct way to throw exceptions from a task continuation, so that that can be passed across the AIB?

    Andy


    MVP

    Sunday, April 28, 2013 9:23 PM

Answers

  • If your intent is to return an IAsyncAction which completes when task1 and its continuation complete, you need something like return continuation1 at the bottom of your create_async lambda.  As it is, the exception which is thrown in the continuation has no where to go to.  An unhandled exception -- even an async one -- will still bring down the process.

    Taking your simplified example, what happens here is the following:

    • TestAsync is called
    • create_async pushes work to a background thread and returns an IAsyncAction which represents the completion of that work
    • The background thread (the lambda) starts a GetFileFromApplicationUriAsync
    • The background thread schedules a continuation to fire when GetFileFromApplicationUriAsync completes
    • The background thread completes and the IAsyncAction which is returned from TestAsync is complete
    • GetFileFromApplicationUriAsync completes
    • The continuation fires
    • The continuation throws
    • No one ever listens to the throw and the process goes down from an unhandled exception.

    If you instead add a return continuation1, the above changes to:

    • TestAsync is called
    • create_async sees that it is composing other async operations (the lambda returns a task and not void) and calls the lambda immediately.
    • GetFileFromApplicationUriAsync is started
    • A continuation is scheduled to fire when GetFileFromApplicationUriAsync completes
    • create_async returns an IAsyncAction which will complete when the scheduled continuation completes
    • GetFileFromApplicationUriAsync completes
    • The continuation fires
    • The continuation throws
    • The throw completes the continuation and is moved as a failure to the IAsyncAction
    • The error moves across the ABI boundary.

    Hope the explanation helps.

    Monday, April 29, 2013 10:21 PM

All replies

  • Link to the project that shows this problem: http://sdrv.ms/11OppNC


    MVP

    Sunday, April 28, 2013 9:30 PM
  • If your intent is to return an IAsyncAction which completes when task1 and its continuation complete, you need something like return continuation1 at the bottom of your create_async lambda.  As it is, the exception which is thrown in the continuation has no where to go to.  An unhandled exception -- even an async one -- will still bring down the process.

    Taking your simplified example, what happens here is the following:

    • TestAsync is called
    • create_async pushes work to a background thread and returns an IAsyncAction which represents the completion of that work
    • The background thread (the lambda) starts a GetFileFromApplicationUriAsync
    • The background thread schedules a continuation to fire when GetFileFromApplicationUriAsync completes
    • The background thread completes and the IAsyncAction which is returned from TestAsync is complete
    • GetFileFromApplicationUriAsync completes
    • The continuation fires
    • The continuation throws
    • No one ever listens to the throw and the process goes down from an unhandled exception.

    If you instead add a return continuation1, the above changes to:

    • TestAsync is called
    • create_async sees that it is composing other async operations (the lambda returns a task and not void) and calls the lambda immediately.
    • GetFileFromApplicationUriAsync is started
    • A continuation is scheduled to fire when GetFileFromApplicationUriAsync completes
    • create_async returns an IAsyncAction which will complete when the scheduled continuation completes
    • GetFileFromApplicationUriAsync completes
    • The continuation fires
    • The continuation throws
    • The throw completes the continuation and is moved as a failure to the IAsyncAction
    • The error moves across the ABI boundary.

    Hope the explanation helps.

    Monday, April 29, 2013 10:21 PM
  • Thanks Bill. Great explanation.

    Working now :)



    Tuesday, April 30, 2013 8:53 AM