This was a very long break since I posted my last web note however it`s time to share some fresh experience with the developer community. Today I want to show one of a handy solution which allowed me to simplify a Task chaining in .NET 4.5.1.
Let`s start from defining a problem which in this case is a Task.ContinueWith<TResult> method and the way how it`s designed for a tasks chaining. So if you want to use a chaining by using this function, all the time you need to access a parent task result property which in many cases causes a lot of redundancy in the code. To demonstrate this I use a simple async WebApi2 GET cities auto-complete function which will query a database and later map result set of entities to collection of data transfer objects (DTO).
// !!BADLY DESIGNED CODE!!
[HttpGet]
public async Task<HttpResponseMessage> Get(string query) {
return await this.cityRepository
.AutocompleteCityAsync(query)
.ContinueWith<HttpResponseMessage>(parentTask =>
{
var cities = parentTask.Result.MapCollection<TCity>();
//Returns HttpResponseMessage.return this.CreateListResponse<TCity>(cities); }); }
As you can see in the example above by awaiting a citiRepository.AutocompleteCityAsync function I started an asynchronous operation (Task). When this function returns a result a next piece of code going to be invoked as I used a ContinueWith<T> function where T is a type of results that my continuation code returns. The problem is that in chained code I need to deal with a previous task itself and the only way to retrieve result of it is to access a Result property directly.
public static Task<TNewResult> Continue<TResult, TNewResult>(
this Task<TResult> task,
Func<TResult, Task<TNewResult>> continuation) { if (task == null) { throw new ArgumentNullException("task"); } if (continuation == null) { throw new ArgumentNullException("continuation"); } return task .ContinueWith(innerTask => continuation(innerTask.Result)) .Unwrap(); }
The continuation code can be easily re-factored by creating a simple extension method for a Task<T> type. As presented in code above, just by centralizing a call of the ContinueWith function with a custom continuation function followed by a Unwrap method a chaining become much cleaner. So then a Continue<TResult, TNewResult> function knows a type of result from a parent task and also know what type a continuation code going to return. Therefor let`s use it an see how a chaining looks like after I use my extension method. Please note that I`m not accessing a parent Task.Result directly and instead of this I working just with a type returned by this task.
[HttpGet] public async Task<HttpResponseMessage> Get(string query) { return await this.cityRepository
.AutocompleteCity(query) .Continue<IQueryable<City>, HttpResponseMessage>(entities => { var cities = entities.MapCollection<TCity>(); return this.CreateListResponse<TCity>(cities); });
}
So as conclusion, just by using a simple extension method, I was able to successfully re-factor a common task chaining logic which results in having more cleaner and easier to understand code.