6

I want to use recent C# 5.0 async methods, however some specific way.

Consider following example code:

public abstract class SomeBaseProvider : ISomeProvider
{
   public abstract Task<string> Process(SomeParameters parameters);    
}

public class SomeConcreteProvider1 : SomeBaseProvider 
{

   // in this method I want to minimize any overhead to create / run Task
   // NOTE: I remove here async from method signature, because basically I want to run 
   // method to run in sync!
   public override Task<string> Process(SomeParameters parameters) {

      string result = "string which I don't need any async code to get";

      return Task.Run(() => result);
   }
}

public class SomeConcreteProvider2 : SomeBaseProvider 
{

   // in this method, it's OK for me to use async / await 
   public async override Task<string> Process(SomeParameters parameters) {

      var data = await new WebClient().DownloadDataTaskAsync(urlToRequest);

      string result = // ... here we convert data byte[] to string some way

      return result;

   }
}

Now how I am going to use async methods (you can ignore fact that consumer actually ASP.NET MVC4 app in my case... it can be anything):

public class SomeAsyncController : AsyncController
{

    public async Task<ActionResult> SomethingAsync(string providerId)
    {
       // we just get here one of providers I define above
       var provider = SomeService.GetProvider(providerId);

       // we try to execute here Process method in async. 
       // However we might want to actually do it in sync instead, if  
       // provider is actually SomeConcreteProvider1 object.
       string result = await provider.Process(new SomeParameters(...));

       return Content(result);
     }
} 

As you can see I have 2 implementations, each one will perform differently: one I want to run in async and do not block thread (SomeConcreteProvider2), while another one I want to be able to run in sync and do not create any Task object etc (which I fail to code in code above, i.e. I do create new Task here!).

There are questions already like How would I run an async Task<T> method synchronously?. However I don't want to run something in sync... I want to avoid any overhead if I know at code time (i.e. before runtime) that some methods implementations will NOT actually be async and will not need to use any threads / I/O completion ports etc. If you check code above, its easy to see that method in SomeConcreteProvider1 basically will construct some string (html), can do it very quickly at the same execution thread. However same method in SomeConcreteProvider2 will need to create Web Request, get Web Response and process it someway and I do want to make at least Web Request in Async to avoid blocking of whole thread during request time (may be quit long time actually).

So the question is: how to organize my code (different method signatures or different implementations, or?) to be able to decide how to execute method and avoid ANY possible overhead which caused for example by Task.Run(...) in SomeConcreteProvider1.Process method?

Update 1: obvious solutions (I think up some of them during question process), such as for example to add some static property to each of providers (say 'isAsyncImplementation') and then check that property to decide how to run method (with await or without await in controller action) have some overhead too :D I want something better if possible :D

Community
  • 1
  • 1
Evereq
  • 1,662
  • 1
  • 18
  • 23
  • P.S. it was really hard to create right title for this question. Will be glad if somebody can fix it :) Thanks – Evereq May 13 '12 at 14:04
  • I believe that creating an async method without any awaits may be faster due to task caching. – SLaks May 13 '12 at 14:08
  • @SLaks you mean that my code in SomeConcreteProvider1.Process already fast enough due to some task caching? – Evereq May 13 '12 at 14:10
  • No; I mean that using `async` instead of manually making a task may be even faster. – SLaks May 13 '12 at 14:12
  • However, it's likely to be fast enough anyway. Have you noticed any perf issues? – SLaks May 13 '12 at 14:12
  • @Slaks: sorry, don't get what you mean by "using async instead of manually making a task". Regarding performance - yes, it's critical section of code because action method of controller will be executed multiple times for single page view and will take relatively long time to complete (can made web requests inside). So that is why want to optimize it in case if some of "providers" do not require to do any web requests at all :) – Evereq May 13 '12 at 15:35
  • Never pre-optimize your code *assuming* that it will not run fast or fast-enough or slower than some other piece of code. If you're really concerned, run some benchmarks or use a profiler. – georgiosd May 14 '12 at 08:56

2 Answers2

10

In your first case, I would recommend returning:

Task.FromResult(result)

which returns a ready-completed task.

http://msdn.microsoft.com/en-us/library/hh194922%28v=vs.110%29.aspx

spender
  • 117,338
  • 33
  • 229
  • 351
  • ah, don't know about that - seems very good approach :D Thanks :) – Evereq May 13 '12 at 14:16
  • hm, it seems like if I replace Task.Run(() => result) with Task.FromResult(result) code compiles, however when I actually execute controller action it return empty content :) I.e. might be some issues related to await of Task.FromResult!? See for example http://social.msdn.microsoft.com/Forums/nl-NL/async/thread/d9146792-1b9a-4807-a42e-29107c281cc4 etc. – Evereq May 13 '12 at 15:31
  • 3
    @EvereQ - MVC4 beta has a bug where it doesn't handle an already-completed Task being returned. The recommended beta workaround is adding a Task.Yield at the top of any such async methods, but Task.Run sounds like a viable workaround for you in this instance. – James Manning May 13 '12 at 16:09
  • Haha thanks James, it's exactly time when one got WTF moment with beta versions :) will try to add yield tomorrow :) – Evereq May 13 '12 at 16:20
  • +1. To answer some of your other questions: using `Task.FromResult` will mean that method will execute synchronously. There is an actual `Task` object created and returned, but it doesn't run anything on the thread pool or anything. I recommend reading the [async/await FAQ](http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx) and my own [async/await intro](http://nitoprograms.blogspot.com/2012/02/async-and-await.html). – Stephen Cleary May 13 '12 at 18:28
  • 1
    Ok, thanks both Spender and @James Manning it finally works. For ASP.NET MVC4 Beta, I was need to add following lines in Web.config: and add await Task.Yield(); in the beginning of corresponding Controller Action to avoid bug in ASP.NET MVC4 Beta :) – Evereq May 14 '12 at 08:47
0

Your design looks fine.

For the SomeConcreteProvider1 case, you can use a TaskCompletionSource

This returns a Task without requiring concurrency.

Also, in the consumer code, await will not yield if the Task has already completed. This is called the "fast path" and effectively makes the method synchronous.

Nick Butler
  • 24,045
  • 4
  • 49
  • 70
  • hm, not sure how to "easy" use TaskCompletionSource here... I.e. you mean to return TaskCompeltionSource and let client decide how to execute code? However question is how client will know is async or sync execution required for given provider if you only return TaskCompletionSource object ? ;-) – Evereq May 13 '12 at 14:21
  • 1
    You can create a `TaskCompletionSource` object in `SomeConcreteProvider1.Process`, set the result / cancellation / exception and return the `Task` property. If you're only ever going to set the result, you can use `Task.FromResult` as spender said. Either way, by returning a `Task` that is already completed, the `await` in the consumer code will not yield - execution will just continue synchronously. – Nick Butler May 13 '12 at 14:27
  • ah, it seems like I got issues to await Task.FromResult for some reason (see my comment to answer by @spender). Will try however approach with TaskCompletionSource – Evereq May 13 '12 at 15:38
  • unbelievably: doing something like: var tcs = new TaskCompletionSource(); tcs.SetResult(html); return tcs.Task; also return empty result when I do await??? Strange – Evereq May 13 '12 at 15:42