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?
i3arnon
  • 113,022
  • 33
  • 324
  • 344
rtorres
  • 2,601
  • 3
  • 27
  • 33
  • `async` is useful if you want to do anything more complicated than your current code. – SLaks Oct 05 '14 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. – Stephen Cleary Oct 05 '14 at 19:21
  • @Stephen Why not? What do you suggest instead? – rtorres Oct 06 '14 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`? – Stephen Cleary Oct 06 '14 at 14:05
  • @Stephen It's just an example in this case to simulate a long-running operation that can be run asynchronously (e.g., a network call). In real-life that'd probably be part of an API call. I didn't mean to communicate that this should be followed as an example to write your web api endpoints by any means. – rtorres Oct 06 '14 at 14:21
  • 1
    @rtorres `Task.Delay` is a the usual example in such cases. – i3arnon Oct 06 '14 at 14:27

2 Answers2

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.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 1
    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. – rtorres Oct 06 '14 at 14:00
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.

Cory Nelson
  • 29,236
  • 5
  • 72
  • 110