Pages

Tuesday, August 12, 2014

Using Task-based Asynchronous Pattern (TAP) in Windows Workflow AsyncCodeActivity

In my previous blog post Invoke Workflow (WF) using Task-based Asynchronous Pattern (TAP) in ASP.NET MVC, I mentioned that I've created a CodeActivity namely GetDeal. This is actually an AsyncCodeActivity which make the http request using the new HttpClient. This is a little tricky because  AsyncCodeActivity is based on the APM pattern (Asynchronous Programming Model pattern aka IAsyncResult pattern aka Begin/End pattern) and the new HttpClient calls are based on TAP pattern (Task-based Asynchronous Pattern). In order to resolve this, I've essentially followed the approach mentioned on msdn here and created an extension method for Task<TResult>.
Within the BeginExecute, I call this method on the Task object returned by HttpClient.GetAsync(string) and then within the EndExecute, I read the HttpResponseMessage from the Task object.
[Note: Since we need the HttpClient instance till the task duration, I've defined the disposing of it within the Task.ContinueWith]

namespace Activities.SmartPea
{
    public sealed class GetDeal : AsyncCodeActivity
    {
        public InArgument<string> ItemName { get; set; }

        public InArgument<string> Zip { get; set; }

        public OutArgument<IEnumerable<Deal>> Deals { get; set; }

        protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback,
                                                     object state)
        {
            var httpClient = new HttpClient();
            var task =
                httpClient.GetAsync(string.Format("http://api.smartpea.com/api/deal/?title={0}&zip={1}",
                                                  ItemName.Get(context), Zip.Get(context)))
                          .ToApm(callback, state);
            task.ContinueWith(obj => httpClient.Dispose());
            return task;
        }

        protected override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
        {
            HttpResponseMessage response = ((Task<HttpResponseMessage>)result).Result;
            IEnumerable<Deal> deals = new List<Deal>();
            if (response.IsSuccessStatusCode &&
                string.Compare(response.Content.Headers.ContentType.MediaType, "application/json",
                               StringComparison.OrdinalIgnoreCase) == 0)
                deals = response.Content.ReadAsAsync<IEnumerable<Deal>>().Result; //.ToObject<IEnumerable<Deal>>();
            Deals.Set(context,deals);
        }
    }

    public static class Extensions
    {
        public static Task<TResult> ToApm<TResult>(this Task<TResult> task, AsyncCallback callback, object state)
        {
            if (task.AsyncState == state)
            {
                if (callback != null)
                {
                    task.ContinueWith(delegate { callback(task); },
                                      CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
                }
                return task;
            }

            var tcs = new TaskCompletionSource<TResult>(state);
            task.ContinueWith(obj =>
                                  {
                                      if (task.IsFaulted) tcs.TrySetException(task.Exception.InnerExceptions);
                                      else if (task.IsCanceled) tcs.TrySetCanceled();
                                      else tcs.TrySetResult(task.Result);

                                      if (callback != null) callback(tcs.Task);
                                  }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
            return tcs.Task;
        }
    }
}

Cheers!
Twisha


No comments:

Post a Comment