3

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();
}
Community
  • 1
  • 1
Benjamin Fox
  • 5,624
  • 5
  • 19
  • 19

3 Answers3

5

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().

svick
  • 236,525
  • 50
  • 385
  • 514
  • Exactly the solution I was looking for - I felt like this was such an obvious use-case, it seemed strange that the API didn't seem to really support it. Luckily I was wrong! – Benjamin Fox May 03 '12 at 23:06
3

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • You're correct in that I meant C# 5.0 targeting .NET 4.5 for the first example. The heads up about the `async targeting pack` is also appreciated - we would like to use async\await but requirements currently have us targeting the 4.0 framework. – Benjamin Fox May 03 '12 at 23:12
1

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?

Community
  • 1
  • 1
Tilak
  • 30,108
  • 19
  • 83
  • 131