Microsoft Transient Fault Handling Application Block ("Topaz") - September 2012 Update

Release Notes

v..5.1.1209.0

Added support for the Task-based Asynchronous Pattern (TAP).

What's New

  • Support for the Task-based Asynchronous Pattern (TAP) using the Task Parallel Library (TPL).
    • Added the ExecuteAsync method that takes a delegate that creates and retries a Task.
  • TransientFaultHandling.Core.dll targets .NET 4.0 in order to support TPL (the Core assembly was previously targeting .NET 3.5).
    • Nevertheless, you can also use it with .NET 4.5 and get the benefits of the new async/await C# keywords.
  • Marked old ExecuteAction method overloads used for doing asynchronous retries as Obsolete.
    • Going forward, if you need to retry calls that use the old APM (asynchronous programing model), you can wrap those into a TaskFactory.FromAsync call and use the new RetryPolicy.ExecuteAsync method instead (see example #3 below).
  • Removed the AllowPartiallyTrustedCallers attribute due to a temporary limitation. If you need to run this code in a partially trusted environment that requires the attribute to be defined, please do not update to this version.

Usage and behavior

  • There are four overloads of RetryPolicy.ExecuteAsync to support passing a function that returns a Task or a Task<TResult>, and optionally pass a cancellation token.
  • The specified taskFunc argument must return a hot task when invoked (a task that is running or scheduled for running) as the RetryPolicy will NOT call the Start method on the task.
    • The returned task should be the one directly involved with calling the service that could experience transient faults. You should not use ExecuteAsync to wrap a higher level task that can initiate additional child tasks, as detecting transient faults may not be done correctly, or the retry of the parent task may cause unexpected behaviors.
  • The taskFunc argument (the function that returns the user initiated retryable task) will not necessarily be invoked in the same synchronization context used when calling ExecuteAsync originally, so if you need to start the task from within the UI thread for example, make sure to schedule it explicitly within the delegate.
  • ExecuteAsync returns a task whose status will eventually be either success (if the user initiated task completes successfully the first time or after retrying), or the result of the last failure if the user task fails due to a non-transient exception or the maximum retry count limit has being reached.
    • You must observe the exception of the task returned by ExecuteAsync to avoid unhandled exceptions.
  • You can add continuations to the task returned by ExecuteAsync. These will be executed after the user initiated task finishes successfully after the retries (or in a faulted state in the case of non-transient exceptions or if the retry count limit is exceeded, or is in a canceled state)
  • If the user initiated task finishes in the Canceled state, the RetryPolicy will not retry, and pass-through this result to the task returned by ExecuteAsync.
  • If you pass a CancellationToken to the different ExecuteAsync overloads, the designed behavior is the following:
    • If you request cancellation after the user task has initiated at least once, then the task returned by ExecuteAsync will contain the result of the last execution after it finishes running (notice that in this case the result will not be Canceled, but will be RanToCompletion or Faulted, as defined by the user task result).
    • If you request cancellation before even starting the first run, then the resulting task will finish in the Canceled state.
  • If an exception is thrown when invoking the taskFunc parameter, then the exception will be propagated synchronously (as opposed to returning a Faulted task) and will not be retried.
    • UPDATE: Since version 5.1.1209.1, if the exception is transient it will be retried, otherwise it will be bubbled up synchronously.
    • The reasoning is that by TAP design guidelines, you should only throw synchronous errors when starting a task only if those are usage errors and could be resolved by fixing the code. For this reason, we do not expect these exceptions to be transient errors. See Task-based Asynchronous Pattern (TAP) for more information.

Examples of usage:

Example 1: Use Topaz to retry a task

retryPolicy.ExecuteAsync(() => client.ReceiveAsync(TimeSpan.Zero))
        .ContinueWith(t =>
        {
            if (t.Exception != null)
            {
                // non-transient exception occurred or retry limit reached
            }
            else
            {
                var message = t.Result;
                // do something with the message.
            }
        });

Example 2: Using the await keyword (new in Visual Studio 2012)

try
{
    var message = await retryPolicy.ExecuteAsync(() => 
        client.ReceiveAsync(TimeSpan.Zero)).ConfigureAwait(false);
    // do something with the message.
}
catch (Exception ex)
{
    // non-transient exception occurred or retry limit reached
}

Example 3: Wrap an APM call inside a task

retryPolicy.ExecuteAsync(
    () => Task<BrokeredMessage>.Factory
        .FromAsync(client.BeginReceive, client.EndReceive, TimeSpan.Zero, null))
        .ContinueWith(t =>
        {
            if (t.Exception != null)
            {
                // non-transient exception occurred or retry limit reached
            }
            else
            {
                var message = t.Result;
                // do something with the message.
            }
        });


Last edited Nov 5, 2012 at 5:58 AM by juliandominguez, version 5

Comments

No comments yet.