5
public class FooHandler  : HttpTaskAsyncHandler
{
    public override async Task ProcessRequestAsync(HttpContext context)
    {
        return await new AdRequest().ProcessRequest();
        // getting error here. "Return type of async type is void"
    }
}

public class FooRequest
{

    public async Task<String> ProcessRequest()
    {
        //return await "foo"; obviously nothing to wait here
    }

}

I want to make a async handler and just want to return a string. How can i get this working? and is there a concise reference to work with Async methods and Tasks?

DarthVader
  • 52,984
  • 76
  • 209
  • 300

3 Answers3

8

A few points:

  • You can await any Task, not just ones returned from async methods.
  • async methods wrap their returned value into a Task<TResult>; if there is no return value, they wrap the return itself into a Task.
  • There are several convenience methods available, e.g., Task.FromResult, if you don't need the overhead of an async method.
  • Only make a method async if you have to use await in it. If you don't need to make the method async, don't.

You may find my async/await intro helpful.

public class FooHandler  : HttpTaskAsyncHandler
{
  public override Task ProcessRequestAsync(HttpContext context)
  {
    return new AdRequest().ProcessRequest();
  }
}

public class AdRequest
{
  public Task<String> ProcessRequest()
  {
    return Task.FromResult("foo");
  }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Stephen If I used TCS instead : `return new TaskCompletionSource()...` - the method will exit only on `setResult`...right ? – Royi Namir Apr 28 '14 at 18:00
  • A bit off - but I was reading again your article , and when you said - "without blocking a thread" - you didn't mention that the chain has to be till top : e.g. :`public void DoSomethingAsync() { doSomething1(); } public async Task doSomething1() { await Task.Delay(100); } ` - here a thread will be blocked (imho) since the top method is not async.(again - imho). what i'm trying to say is that this should be added. – Royi Namir Apr 28 '14 at 18:35
  • In your example, no threads are blocked. I'm not sure if I understand your question; could you post an actual SO question? – Stephen Cleary Apr 28 '14 at 18:43
  • This is a bad example because isn't actually using a task so those many developers who just copy code snippets from web will actually write blocking code. It also worth mentioning especially in a networked environment that object used in asynchronous methods should not be disposed before execution ended. I'll add a sample method in an answer. – user3285954 Jan 09 '15 at 14:47
  • @user3285954: The op specifically asked how to implement an asynchronous interface method synchronously (just returning a string). The cleanest way to do that *is* `Task.FromResult`. – Stephen Cleary Jan 10 '15 at 00:13
  • @Stephen Cleary This is speculation, op never mentioned that has to be done synchronously unless there was an edit after your post. Returning "only" a string doesn't mean call has to be synchronous. The await keyword doesn't control whether a call is synchronous or asynchronous, is only a syntactical sugar what replaces Task.Wait and similar. I could also speculate that because op uses an AdRequest class what is probably returning an ad (like an advertisement) the method is likely reading from a file or a database and both are IO so does make sense to use tasks. – user3285954 Jan 10 '15 at 10:33
1

You shouldn't "return" the Task, the compiler will do it implicitly as it is an async function:

public override async Task ProcessRequestAsync(HttpContext context)
{
    await new AdRequest().ProcessRequest();
}

public async Task<String> ProcessRequest()
{
    return "foo";
}

This is another way, closer to what you were trying to do: (without async/await)

public override Task ProcessRequestAsync(HttpContext context)
{
    return new AdRequest().ProcessRequest();
}

public Task<String> ProcessRequest()
{
    return Task.Return("foo");
}

A general reference to async is here Essentially adding the async modifier to a method, makes it return a Task implicitly. If you return an int, it will turn it into a Task<int>. await does the opposite, turning a Task<int> into an int.

Fil
  • 1,766
  • 1
  • 15
  • 15
0

This is a truly asynchronous method:

public Task<string> ProcessRequest()
{
    var textFile = File.OpenText("file.txt");
    var readTask = textFile.ReadToEndAsync();

    readTask.ContinueWith(previousTask => textFile.Dispose());

    return readTask;
}

If you run this method with a large file or a file on a slow drive the execution will return to caller long before file reading ends. In Stephen Cleary's example the caller will get back control only when the result ("foo") is finished calculating.

Dispose must be in ContinueWith because the method execution will return to caller before file reading is complete so file can't be closed in ProcessRequest method.

One can of course start their own task.

public Task<string> ProcessRequest(CancellationToken cancellationToken)
{
    var readTask = Task.Run(() =>
    {
        using (var textFile = File.OpenText("file.txt"))
        {
            var text = textFile.ReadToEnd();

            cancellationToken.ThrowIfCancellationRequested();

            var processedText = text.Replace("foo", "bar");

            return processedText;
        }
    });

    return readTask;
}

It is a good practice to have a CancellationToken and periodically check if cancellation was requested to allow long running operarions to be cancelled.

Edit 1

As @Stephen Cleary highlighted the first sample and this result in approximately or maybe exactly the same CIL:

public async Task<string> ProcessRequest()
{
    using (var textFile = File.OpenText("file.txt"))
    {
        var s = await textFile.ReadToEndAsync();

        return s;
    }
}

Basically the compiler will transform the code following await textFile.ReadToEndAsync() into ContinueWith.

Each syntax has its benefits, my preference is that 1-2 lines (i.e. dispose and log) go into ContinueWith, more complex continuation uses await.

user3285954
  • 4,499
  • 2
  • 27
  • 19
  • Your first example is no better than `using (var textFile = ...) { return await textFile.ReadToEndAsync(); }` - except the `using`+`await` approach is IMO clearer. The second example is more dangerous; in general, you should avoid `Task.Run` on ASP.NET. – Stephen Cleary Jan 10 '15 at 00:16
  • Correct, await is basically nothing else than a syntactical sugar to replace Wait with ContinueWith. The background is probably more complex but the core of the difference is this. async was a wrong choice for this keyword because many devs think it controls whether the method is asyncrhonous or not. The reason I avoid await in samples and in code too is that makes the code harder to understand for those who are not much into TPL, hides what is happening. I only use it as a last resort when the continuation would be too complex, but for 1-2 lines like in this example ContinueWith is perfect. – user3285954 Jan 10 '15 at 10:46
  • @Stephen Cleary Why should Task.Run be avoided in ASP.Net? Not all API are task based and if one is using an api like that they have no other option than tasks or they'd have to block the ASP.Net thread what is worse than starting a new task (what is actually a thread). Staring a thread would be the same except more complex code is needed to manage. Are you hinting that request may time out while task is running? – user3285954 Jan 10 '15 at 10:48
  • 1
    Completely disagree. "Asynchronous" does not mean "runs on another thread", and `await` creates far, **far** more maintainable code than `ContinueWith`. `Task.Run` should be avoided specifically because it does use another thread, interfering with the ASP.NET thread pool. – Stephen Cleary Jan 11 '15 at 03:57
  • When using tasks what else other than a thread can make a call asnchronous? The thread might be in another process but is still a thread eventually. Could you elaborate on ASP.Net and Task.Run? I've never read a warning about this, the general recommendation seems to be that unless the calls are short and CPU bound using tasks is better. The answer to use or not use tasks i.e. in a controller method is not that simple but there are many articles on this topic i.e. http://www.asp.net/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4 – user3285954 Jan 11 '15 at 16:12
  • await seems very simple but like any solution what hides the complexity it has a few risks. One is that makes understanding the code harder for people who are new to tasks. This is confirmed by the many questions around async and await keywords, whereas the same code with ContinueWith would make obvious what is happening, when is the code after awaited method called and why T has to be returned instead of Task. Another problem is that await hides the fact that the continuation may and ususally does run on a different thread. This has to be handled explicitly in Winforms and other UI. – user3285954 Jan 11 '15 at 16:20
  • Have a question about await and ConfigureAwait(false). Would the same deadlock occur if ContinueWith would be used instead of await? Already read your article: http://msdn.microsoft.com/en-us/magazine/jj991977.aspx – user3285954 Jan 11 '15 at 16:33
  • As I describe on my blog, [there is no thread](http://blog.stephencleary.com/2013/11/there-is-no-thread.html) used by a truly asynchronous operation. In particular, `async` does *not* equal multithreading; and in fact, `async` *minimizes* (not increases) the number of threads you need. – Stephen Cleary Jan 11 '15 at 18:14
  • I walk through an [ASP.NET `Task.Run` example](http://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html) on my blog. The [official word from the ASP.NET team is to avoid `Task.Run`](http://channel9.msdn.com/Events/aspConf/aspConf/Async-in-ASP-NET). – Stephen Cleary Jan 11 '15 at 18:16
  • There are a lot of questions about `async` because it is a new technology and asynchronous code is becoming a requirement in this mobile-first, cloud-first world. `async` is the cleanest, most maintainable solution, so it is what developers are (correctly) adopting. There are fewer questions around `ContinueWith` because devs are (correctly) moving *away* from that approach. I've had to maintain very large async code bases both before and after `async`, and I can say from experience that there's no contest. – Stephen Cleary Jan 11 '15 at 18:28
  • The deadlock is caused by a thread blocking a context while waiting for a task to complete, when the task needs to run in the context in order to complete. `await` can cause that deadlock unless you use `ConfigureAwait(false)`; and `ContinueWith` can cause that deadlock if you schedule it to `TaskScheduler.FromCurrentSynchronizationContext()`. – Stephen Cleary Jan 11 '15 at 18:30
  • Thank you for the links, good reading. Will recommend to many devs. – user3285954 Jan 11 '15 at 21:54