Potential pitfalls to avoid when passing around async lambdas

Potential pitfalls to avoid when passing around async lambdas

Rate This
  • Comments 7

One of the really useful capabilities of the new async methods feature in C# and Visual Basic is the ability to write async lambdas and anonymous methods (from here on in this post, I’ll refer to both of these as async lambdas, since the discussion applies equally to both).  This allows you to easily get a delegate to represent an asynchronous operation, e.g.

Func<Uri,Task<string>> getContentsLowerCaseAsync = async url =>
{
    string contents = await DownloadString(url);
    return contents.ToLower();
};

Async methods in C# and Visual Basic can return void, Task, or Task<TResult>, which means they can be mapped to delegates that return void, Task, or Task<TResult>.  This is very powerful, but it can also lead to subtle bugs if you’re not careful.

Most methods today that accept as a parameter a delegate that returns void (e.g. Action, Action<T>, etc.) expect the work of that delegate to be completed by the time the delegate completes.  As a simple example, consider a timing helper function, whose job it is to time how long a particular piece of code takes to execute:

public static double Time(Action action, int iters=10)
{
    var sw = Stopwatch.StartNew();
    for(int i=0; i<iters; i++) action();
    return sw.Elapsed.TotalSeconds / iters;
}

With this function, if I then run the following code:

static void Main()
{
    double secs = Time(() =>
    {
        Thread.Sleep(1000);
    });
    Console.WriteLine("Seconds: {0:F7}", secs);
}

I see this written out to the console:

Seconds: 0.9999956
Press any key to continue . . .

That’s what I’d expect: we asked to sleep for one second, and that’s almost exactly what the timing showed.  But now consider an alternate piece of code:

static void Main()
{
    double secs = Time(async () =>
    {
        await Task.Delay(1000);
    });
    Console.WriteLine("Seconds: {0:F7}", secs);
}

When I run this, I see the following written out to the console:

Seconds: 0.0000341
Press any key to continue . . .

Huh? Here we have an async method that’s awaiting a Task that won’t complete for a second, so this asynchronous method’s execution should also be at least a second, and yet the timer is telling us that it took only 34 microseconds?  What’s going on?

To understand this effect, we need to remember how async methods operate.  When you invoke an async method, it starts running synchronously.  If the method doesn’t have any awaits in it, or if all of the awaits in the method are on awaitables that are already completed by the time they’re awaited, then the method will run entirely synchronously.  However, when the method encounters the first await that yields, the async method returns.  In the case of an async method that returns a Task or a Task<TResult>, the method at this point returns the Task or Task<TResult> that represents the async method’s execution, and the caller can use that task to wait synchronous (e.g. Wait()) or asynchronously (e.g. await, ContinueWith) for the method to asynchronously complete.  In the case of a void method, though, no handle is handed back.  Async void methods are thus often referred to as “fire and forget.”

Now with that background, consider what’s happening with our timing function.  Our Time method accepts an Action, so the compiler is going to map our “async () => { … }” to being a void-returning async method, and the Action passed into the Time method will be for that void method.  Thus, when Time invokes the Action, the Action will return as soon as it hits the first await that yields, which is our await for the delay task.  This means that we’re really only timing the invocation of the async method up until the await, but not including the time to await the task or what comes after it.

We can fix this by modifying our Time function to accept a Func<Task> instead of an Action:

public static double Time(Func<Task> func, int iters=10)
{
    var sw = Stopwatch.StartNew();
    for (int i = 0; i < iters; i++) func().Wait();
    return sw.Elapsed.TotalSeconds / iters;
}

Now when I compile and run our async lambda, I get the following output that’s what I’d expect:

Seconds: 1.0078671
Press any key to continue . . .

Void-returning methods aren’t the only potentially problematic area; they’re just the easiest example to highlight, because it’s very clear from the signature that they don’t return anything and thus are only useful for their side-effects, which means that code invoking them typically needs them to run to completion before making forward progress (since it likely depends on those side-effects having taken place), and async void methods defy that.

A more complicated but still problematic example is a generic method that accepts an Action as a parameter and returns a Task, or that accepts a Func<…,TResult> as a parameter and returns a Task<TResult>, such as Task.Factory.StartNew.  Consider the following:

var t = Task.Factory.StartNew(() =>
{
    Thread.Sleep(1000);
    return 42;
});

Here StartNew accepts a delegate of type Func<int>, and returns a Task<int> representing the execution of the Func<int> delegate.  Makes sense.  But now consider the following:

var t = Task.Factory.StartNew(async () =>
{
    await Task.Delay(1000);
    return 42;
});

Any guesses as to what the type of ‘t’ is?  StartNew accepts a Func<TResult> and returns a Task<TResult>.  We’re passing in an async lambda that will give back a Task<int>, which means the TResult in Func<TResult> is actually Task<int>, such that the delegate provided to StartNew is a Func<Task<int>>.  That means that this call to StartNew is actually returning a Task<Task<int>>.  The task created by StartNew will invoke the Func<Task<int>>, which will run synchronously until the first await that yields, at which point the Func<Task<int>> will return, handing back the result Task<int> that represents the async lambda’s execution.  StartNew will then complete the Task<Task<int>> that it handed back, since the delegate associated with that task has completed its synchronous execution.  If I wrote code that depended on the returned task’s completion to mean that the async lambda had completed, I’d be sorely disappointed.  It’s actually the returned task’s Result (which is itself a Task<int>) that represents the async lambda.

There are a few ways to address this, such as using the Unwrap method: 

var t = Task.Factory.StartNew(async () =>
{
    await Task.Delay(1000);
    return 42;
}).Unwrap();

For more information, see my previous blog post on this (and on how Task.Run differs in behavior here from Task.Factory.StartNew) at http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx.

Leave a Comment
  • Please add 5 and 6 and type the answer here:
  • Post
  • Excellent, this it what makes me want to develop more and more and thanks for the last articles ...

    Keep us informed!!!

    Cheers ..

  • Hi,

    It's not related to this blog entry, but I've got a question for you.

    Some time ago I've got an answer I was looking for:  i.e. that async / await will be supported for web service calls done through a proxy generated by ChannelFactory (see social.msdn.microsoft.com/.../8ec1b6ef-dbd8-46e6-b0b9-db7bfe80db06).

    My question is:  can you show how to do that in .NET 4.5?  And...  could we do something similar in .NET 4.0?

    I'm on a project right now that would really beneficiate from using async WS calls with IO Ports (i.e. real async, not a thread spinning while waiting for the WS to come back) but we are on .NET 4.0!

    Cheers and keep up the good work!

  • Hi Vincent-Philippe-

    re: can you show how to do that in .NET 4.5

    It happens by default.  In Visual Studio 11 Developer Preview, when you use Add Service Reference to create a client proxy, it automatically generates Task-based methods.

    re: can you show how to do that in .NET 4

    In the Async CTP, there is a sample implementation of an extension to the Add Service Reference that will generate Task-based endpoints.  But you could also just have it generate Begin/EndXx methods (you need to configure the options in Add Service Reference to spit out async endpoints) which you then wrap with Task.Factory.FromAsync or similar.

    I hope that helps.

  • Hi Stephen,

    Thanks for your answer.

    As I mentionned, we are using 'ChannelFactory', hence we're not using Add Web Service reference.  We are exporting service / data contracts in a shared assembly and simply use ChannelFactory to create a proxy out of the service contract interface.  Hence the interface looks sync (no task in sight) and the eventual call to an 'asyncable' network connection is buried in the implementation of the proxy generated by ChannelFactory (.NET Fx).  It's a very handy API because you can stay in sync with your contracts without refreshing them all the time (especially useful if you have many services).

    That scenario was supposed to be supported in .NET 4.5 (according to social.msdn.microsoft.com/.../8ec1b6ef-dbd8-46e6-b0b9-db7bfe80db06).  I do not know if there's a twist to do somewhere for it to work?

    I've seen service contracts with Task in the Async Fx.  Not too sure how that translates into the WSDL?  Is it another way to declare an operation async?  What we really need is to have the operation sync on the server side (hence being able to return something) but async on the client side, without relying on VS Service Reference code generation.

    Could you point me to something doing that?

    Thank you!

  • Hi Vincent-Philippe-

    re: "What we really need is to have the operation sync on the server side (hence being able to return something) but async on the client side"

    Why does the server-side need to be synchronous?  The client-side and the server-side should be entirely independent when it comes to asynchrony.  You could have sync or async on the client, and sync or async on the server, and they don't need to match.

    re: "we are using 'ChannelFactory', hence we're not using Add Web Service reference"

    Sorry, I misunderstood what you were trying to do. Have you tried the .NET 4.5 Developer Preview?  I just tried this out, and using ChannelFactory does work there.  My server-side interface looked like:

       [ServiceContract]

       public interface IService1

       {

           [OperationContract]

           Task<string> GetDataAsync(int value);

       }

    giving me an asynchronous design on the server (but I could have just have easily made that synchronous, returning string instead of Task<string>).  Then my client-side interfaces looked like:

       public interface IService1Channel : IService1, IClientChannel

       {

       }

       [ServiceContractAttribute(ConfigurationName = "IService1")]

       public interface IService1

       {

           [OperationContractAttribute(Action = "tempuri.org/.../GetData", ReplyAction = "tempuri.org/.../GetDataResponse")]

           string GetData(int value);

           [OperationContractAttribute(Action = "tempuri.org/.../GetData", ReplyAction = "tempuri.org/.../GetDataResponse")]

           Task<string> GetDataAsync(int value);

       }

    and then after implementing my service:

       public class Service1 : IService1

       {

           public async Task<string> GetDataAsync(int value)

           {

               await Task.Delay(1000);

               return string.Format("After a second, the value was {0}.", value);

           }

       }

    and creating the appropriate configuration in my app.config, I was able to successfully run code that used ChannelFactory, e.g. both:

       var factory = new ChannelFactory<IService1Channel>("BasicHttpBinding_IService1");

       var channelClient = factory.CreateChannel();

       Console.WriteLine(channelClient.GetData(42));

    and

       var factory = new ChannelFactory<IService1Channel>("BasicHttpBinding_IService1");

       var channelClient = factory.CreateChannel();

       Console.WriteLine(channelClient.GetDataAsync(42).Result);

    I hope that helps.

  • Stephen, Hi

    for your very first code snippet I think you meant

     var wc = new System.Net.WebClient();

     Func<Uri, Task<string>> getContentsLowerCaseAsync = async url =>

     {

         string contents = await wc.DownloadStringTaskAsync(url);

         return contents.ToLower();

     };

    thanks for excellent guidance. When's your book coming out [if not why not] ??

    cheers,

    Dick

  • @Dick:  Thanks, though I wasn't specifically talking about WebClient.DownloadString.  Imagine DownloadString were a static method that just did "return new HttpClient().GetStringAsync(url);".  As for a book, I have no plans, but thanks for the encouragement.

Page 1 of 1 (7 items)