Reputation: 5780
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.
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
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
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
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