Handling Exceptions When Using WinRT Async Functions

Games should always use proper exception handling when making Xbox Service API calls. Common real-world conditions can result in a request to Xbox Live returning failure codes such as:

Games need to capture these exceptions and handle them appropriately.

How the Xbox Service API’s throw exceptions and proper handling:

The following example shows how GetUserProfileAsync can throw an exception when called and how to handle it.

      1.	try
      2.	{
      3.	    // Surround Xbox Service API calls in a try/catch at the call level
      4.	    // or at a higher level in the callstack.
      5.
      6.	    auto pAsyncOp = requester->ProfileService->GetUserProfileAsync(""); //invalid Xbox User Id
      7.
      8.	    // task chain goes here
      9.	}
      10.	catch( Platform::COMException^ e )
      11.	{
      12.	    // Failed
      13.	    //
      14.	    // In this example, failure caused by an invalid argument
      15.	}  

Let us look at the pattern for proper exception handling for Xbox Service API both when using PPL (Parallel Patterns Library) as well as when not using PPL. By following these examples, you can reduce the time spent on troubleshooting Xbox Service API crashes as a result of network errors.

      1.	auto pAsyncOp = requester->ProfileService->GetUserProfileAsync("abc123"); //passing invalid Xbox User Id;
      2.
      3.	create_task( pAsyncOp )
      4.	.then( [this] (task "XboxUserProfile^" resultTask) 
      5.	{
      6.	    // Oops, I forgot my exception handling code here.
      7.	    // If I don't call resultTask.get() and catch any potential exception it may throw,
      8.	    // then PPL will report an unobserved exception.  That unobserved exception will cause your
      9.	    // app to crash.
      10.	});  

If your callstack contains Concurrency::_ReportUnobservedException(), this is a good indication that there is a bug in the title’s code. PPL creates tasks, which can be followed by other tasks. In the example above, create_task() builds the task to call GetUserProfileAsync() and the .then() creates the following task.

These are often referred to as the antecedent task (first one) and the continuation (second). In the example, the continuation task does not have any error handling. The runtime terminates the app if a task throws an exception and that exception is not caught by the task or one of its continuations.

When it comes to continuation tasks, note that there are actually two different kinds. One kind, the task-based continuation, takes the previous task as the input argument. This task always runs, even if the antecedent task throws an exception. To get the result of the antecedent task, you must call .get() on the argument. The second, value-based, receives the output of the previous task directly. However value based continuations aren’t run at all if the antecedent throws an exception.

To prevent crashes it is recommended that you use a task-based continuation at the end of your continuation chain and surround all concurrency::task::get() or concurrency::task::wait() calls in try/catch blocks to handle errors that can be recovered from.

Here are two examples:

Value-based continuation example

      1.	create_task( pAsyncOp )
      2.	.then( [this] (XboxUserProfile^ result) // Value-based continuation
      3.	{
      4.	    // The task completed successfully, do something here.
      5.	    // if the task didn't complete successfully, you'd better have a task-based
      6.	    // continuation at the end of the continuation chain or the app will crash.
      7.	})
      8.	.then( [this] (task (void) previousTask) // Task-based continuation
9.	{
10.	    try
11.	    {
12.	        // IMPORTANT TO HAVE THIS.
13.	
14.	        // call concurrency::task::get and handle any unobserved exception
15.	        // so the application doesn't crash.
16.	        previousTask.get();
17.	
18.	        // success, continue
19.	    }
20.	    catch (Platform::Exception^ ex)
21.	    {
22.	        // concurrency::task::get threw an exception
23.	        // safely handle the error here
24.	        // By handling this exception, you ensure your application will not
25.	        // crash when calling Xbox Service APIs
26.	    }
27.	});  

Value-based continuation example

      1.	create_task( pAsyncOp )
      2.	.then( [this] (XboxUserProfile^ result) // Value-based continuation
      3.	{
      4.	    // The task completed successfully, do something here.
      5.	    // if the task didn't complete successfully, you'd better have a task-based
      6.	    // continuation at the end of the continuation chain or the app will crash.
      7.	})
      8.	.then( [this] (task(void) previousTask) // Task-based continuation
9.	{
10.	    try
11.	    {
12.	        // IMPORTANT TO HAVE THIS.
13.	
14.	        // call concurrency::task::get and handle any unobserved exception
15.	        // so the application doesn't crash.
16.	        previousTask.get();
17.	
18.	        // success, continue
19.	    }
20.	    catch (Platform::Exception^ ex)
21.	    {
22.	        // concurrency::task::get threw an exception
23.	        // safely handle the error here
24.	        // By handling this exception, you ensure your application will not
25.	        // crash when calling Xbox Service APIs
26.	    }
27.	});  

There is a third solution – use value-based continuations completely, but call .get() or .wait() on another thread and catch the exception there. Here’s a simple example:

      1.	auto getProfileTask = create_task( pAsyncOp )
      2.	.then( [this] (XboxUserProfile^ result) // Value-based continuation
      3.	{
      4.	    // The task completed successfully, do something here.
      5.	});
      6.	// Note the lack of a task-based continuation with error handling at the end
      7.
      8.	// You may call .get() or .wait() on a value-based only chain, but
      9.	// must ensure you surround the call in a try/catch block and handle errors
      10.	try
      11.	{
      12.	    getProfileTask.get();     // or getProfileTask.wait();
      13.	}
      14.	catch (Platform::Exception^ ex)
      15.	{
      16.	    // concurrency::task::get threw an exception
      17.	    // safely handle the error here
      18.	    // By handling this exception, you ensure your application will not
      19.	    // crash when calling Xbox Service APIs
      20.	}  

If you are using AsyncOperationCompletionHandler or AsyncActionCompletionHandler instead of PPL, you must also properly handle those errors to avoid crashes. B elow is an example showing how to handle errors.

      1.	try
      2.	{
      3.	    // Example is making a service call with an invalid XboxUserId which will result in an error even before the async code.
      4.	    // The completion handler properly detects the error and does not crash the app.
      5.	    requester->ProfileService->GetUserProfileAsync("abc123")->Completed
      6.	        = ref new AsyncOperationCompletedHandler(XboxUserProfile^)
        ([=](IAsyncOperation(XboxUserProfile^)^ operation, Windows::Foundation::AsyncStatus status)
7.	    {
8.	        if( status == Windows::Foundation::AsyncStatus::Completed)
9.	        {
10.	            // Always check the AsyncStatus before calling GetResults().
11.	            // If status is not AsyncStatus::Completed, calls to operation->GetResults()
12.	            // may throw an exception.
13.	            // You can also surround this call in a try/catch block for added safety.
14.	
15.	            XboxUserProfile^ result = operation->GetResults();
16.	
17.	            // success, do something with the result
18.	        }
19.	        else if( status == Windows::Foundation::AsyncStatus::Error )
20.	        {
21.	            // Failed
22.	        }
23.	    });
24.	}
25.	catch ( Platform::COMException^ ex )
26.	{
27.	    // What is this try/catch block for?
28.	    //
29.	    // Xbox Service APIs do have some code that runs synchronously and errors need
30.	    // to be safely handled.  In this example, if “” was passed instead of “abc123”,
31.	    // then an invalid argument exception would be thrown when calling GetUserProfileAsync
32.	// See the next section for more a more detailed explanation.
33.	    //
34.	    // Note: this catch block will NOT catch exceptions thrown within the completion handler.
35.	}  

If you are using the paging APIs such as AchievementResult, LeaderboardResult, InventoryItemResult, and TitleStorageBlobMetadataResult objects, they all contain a GetNextAsync() method to request the next page of results. There is a special case, when no more data is available, that triggers an exception when calling GetNextAsync(). This exception is thrown during the synchronous execution of GetNextAsync(). In this case, the GetNextAsync method throws INET_E_DATA_NOT_AVAILABLE (0x800C0007).

To avoid an unhandled exception and crash, ensure that the GetNextAsync() method is wrapped in a try/catch block and gracefully handle the INET_E_DATA_NOT_AVAILABLE case.

      1.	try
      2.	{
      3.	    // AchievementResult^ LastResult
      4.
      5.	    // Get next page of achievement results
      6.	    if(LastResult != nullptr)
      7.	    {
      8.	        auto getNextPage = LastResult->GetNextAsync(10);
      9.
      10.	        // create_task( getNextPage ) ...
      11.	    }
      12.	}
      13.	catch (Platform::Exception^ ex)
      14.	{
      15.	    if (ex->HResult == INET_E_DATA_NOT_AVAILABLE)
      16.	    {
      17.	        // we hit the end of the achievements
      18.	    }
      19.	    else
      20.	    {
      21.	        // failed for unexpected reason
      22.	    }
      23.	}  

The examples above focused on async operations, but the same principles should be applied to async actions.

Further reading is recommend on asynchronous programming in C++ using PPL and exception handling in the Concurrency Runtime on MSDN to gain a more in depth understanding of exception handling requirements.

For further reading, see the following references: