Benjamin Fox
Benjamin Fox

Reputation: 5780

Merging .NET 4.0 Tasks/continuations of different types

I'm currently implementing a System.Web.Http.IActionFilter which calls an internal service to determine whether the current request can continue. The problem I'm having is returning a Task<T1> based on a piece of logic encapsulated by a Task<T2>.

An example might help.

The internal service API is implemented using Tasks. The logic is trivial using .NET 4.5's async/await:

public async Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
    UserAuthenticationResult authResult = await HitInternalServiceAsync();

    if (!authResult.IsAuthenticated)
    {
        throw new HttpResponseException("User is not authenticated", HttpStatusCode.Unauthorized);
    }

    return await continuation();
}

However it is more difficult with the old Task API in .NET 4.0;

public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
    return HitInternalServiceAsync()
            .ContinueWith(t1 => {
                UserAuthenticationResult authResult = t1.Result;

                if (!authResult.IsAuthenticated)
                {
                    throw new HttpResponseException("User is not authenticated", HttpStatusCode.Unauthorized);
                }

                //Hack hack - this blocks the thread until the task retuned by continuation() completes
                return continuation().Result;
            });
}

The difficult part comes when the authentication check has succeeded - I then want to await the task returned by the continuation function.

Using .NET 4.0 it looks like I have explicitly block when waiting for the continuation() task to complete, rather than instructing the Tasks API to automatically continue with the continuation() task when my task is done.

The question: Is this the only way to implement this behaviour in .NET 4.0?

Given a sufficiently complicated internal service API, I can easily see the number of Tasks waiting on other Tasks multiplying quickly.

EDIT: Looks like the above 4.0 code isn't viable either - because the continuation lambda does not execute in the ASP.NET thread context services like HttpContext.Current are not available. A better implementation would be...

public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
    Task<UserAuthenticationResult> authResultTask = HitInternalServiceAsync();

    var authResult = authResultTask.Result;

    if (!authResult.IsAuthenticated)
    {
        throw new HttpResponseException("User is not authenticated", HttpStatusCode.Unauthorized);
    }

    return continuation();
}

Upvotes: 3

Views: 1720

Answers (3)

Stephen Cleary
Stephen Cleary

Reputation: 457137

The question: Is this the only way to implement this behaviour in .NET 4.0?

async/await is a C# 5.0 feature, not a .NET 4.5 feature. It does make use of some types introduced in .NET 4.5, but there's no other reason it would require a new runtime.

svick's answer is the best if you're using VS2010 (C# 4.0).

However, there is another option if you're using VS11 Beta (C# 5.0): you can use the async targeting pack to write async/await code that runs on .NET 4.0. The targeting pack has those new types for .NET 4.0.

Upvotes: 3

svick
svick

Reputation: 244978

Your problem is that if you don't use Result, ContinueWith() will return Task<Task<HttpResponseMessage>> and not the Task<HttpResponseMessage> you need.

Fortunately, there already is a method that converts any Task<Task<T>> to Task<T>: Unwrap(). So just return continuation(); from the ContinueWith() lambda and then call Unwrap() on the result.

If you want the continuation to execute in the ASP.NET context, you can use TaskScheduler.FromCurrentSynchronizationContext().

Upvotes: 5

Tilak
Tilak

Reputation: 30728

instead of continuation().Result use continuation().Wait()

task.wait is the appropriate way to block a task.

As per MSDN documentation, Task.Wait Method: Waits for the Task to complete execution.

http://msdn.microsoft.com/en-us/library/dd235635.aspx

Folowing seems related question, that answers Do the new C# 5.0 'async' and 'await' keywords use multiple cores?

Upvotes: 1

Related Questions