7

If I wanted to write a non-blocking web api action by returning a Task object, I could do it with or without using the async keyword as such:

Using async

public async Task<HttpResponseMessage> Get()
{
    Func<HttpResponseMessage> slowCall = () =>
    {
        Thread.Sleep(2000);
        return Request.CreateResponse(HttpStatusCode.OK, "Hello world");
    };

    var task = Task<HttpResponseMessage>.Factory.StartNew(slowCall);
    return await task;
}

Without using async

public Task<HttpResponseMessage> Get()
{
    Func<HttpResponseMessage> slowCall = () =>
    {
        Thread.Sleep(2000);
        return Request.CreateResponse(HttpStatusCode.OK, "Hello world");
    };

    var task = Task<HttpResponseMessage>.Factory.StartNew(slowCall);
    return task;
}

They both work properly. However, every example I've seen (online and in books) on writing a web api action that returns a Task uses the async keyword. Granted, I understand that gives you more flexibility as it allows you to control what you want to "await" on and what not. But assuming your functionality could be effectively handled either way,

  • Is there any advantage to using one approach vs the other?
  • Should I always use the async keyword (and if so why)?
  • Or is it irrelevant?
6
  • async is useful if you want to do anything more complicated than your current code. Commented Oct 5, 2014 at 17:19
  • 1
    I know this is just example code, but I must point out that you shouldn't use TaskFactory.StartNew in ASP.NET. Commented Oct 5, 2014 at 19:21
  • @Stephen Why not? What do you suggest instead? Commented Oct 6, 2014 at 13:51
  • @rtorres: You should use naturally-asynchronous APIs. If you use Task.Run (or even worse, StartNew), you're just trading one thread for another. Let me turn the question around: what benefit do you get from await with Task.Run or StartNew? Commented Oct 6, 2014 at 14:05
  • 1
    @rtorres Task.Delay is a the usual example in such cases. Commented Oct 6, 2014 at 14:27

2 Answers 2

5

The async keyword allows you to use an await in your method by creating a state machine. If you can manage returning an asynchronous task without using it you can go ahead and remove it because it has some (very small) overhead. Keep in mind though that it's only useful in a few cases. Your return await is one of them.

Another difference is how exceptions are handled. If there's an exception in the synchronous part of the method and it's marked as async the exception will be stored in the returned task. Without the keyword the exception would be thrown regularly. For example in this case there's a big difference:

var task = Get(); // unhandled exception without async
try
{
    var result = await task; // handled exception with async
}
catch
{
}

My recommendation is to use the async keyword even when you don't absolutely need to*, because most developers don't understand the difference and the value in the optimization is mostly negligible.


* Unless you and your teammates really know what you're doing.

Sign up to request clarification or add additional context in comments.

1 Comment

Interestingly enough, I ran some JMeter tests to compare both options and the "async" one performed faster (as measured by the average response time). Not sure why (I would have expected it to be slower because of the state machine overhead) ... I may have set up the tests incorrectly. In any case, considering performance is similar, the ability to debug (along with the extra flexibility to control awaitable functionality) is a good reason to prefer the async approach so I agree with your recommendation.
3

There is one advantage to bypassing the await and returning the Task directly: performance. You won't allocate or process the state machine that goes with an async method. There are, however, some subtle differences when exceptions are involved.

In the async example, any exceptions thrown will be enclosed in a Task. This is generally what people assume will happen when they call a Task-returning method. In the sync example, exceptions will be thrown immediately upon invocation.

This will also have an effect on the exception's stack trace. In the async example, it will show Get(). In the sync example, it will show your anonymous delegate or worse, some internal crap in Task.Factory.StartNew with no actual reference to your actual code. This can result in being a little more difficult to debug.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.