Should I expose asynchronous wrappers for synchronous methods?

Should I expose asynchronous wrappers for synchronous methods?

Rate This
  • Comments 34

Lately I’ve received several questions along the lines of the following, which I typically summarize as “async over sync”:

In my library, I have a method “public T Foo();”.  I’m considering exposing an asynchronous method that would simply wrap the synchronous one, e.g. “public Task<T> FooAsync() { return Task.Run(() => Foo()); }”.  Is this something you’d recommend I do in my library?

My short answer to such a question is “no.”  But that doesn’t make for a very good blog post.  So here’s my longer, more reasoned answer…

Why Asynchrony?

There are two primary benefits I see to asynchrony: scalability and offloading (e.g. responsiveness, parallelism).  Which of these benefits matters to you is typically dictated by the kind of application you’re writing.  Most client apps care about asynchrony for offloading reasons, such as maintaining responsiveness of the UI thread, though there are certainly cases where scalability matters to a client as well (often in more technical computing / agent-based simulation workloads).  Most server apps care about asynchrony for scalability reasons, though there are cases where offloading matters, such as in achieving parallelism in back-end compute servers.

Scalability

The ability to invoke a synchronous method asynchronously does nothing for scalability, because you’re typically still consuming the same amount of resources you would have if you’d invoked it synchronously (in fact, you’re using a bit more, since there’s overhead incurred to scheduling something ), you’re just using different resources to do it, e.g. a thread from a thread pool instead of the specific thread you were executing on.  The scalability benefits touted for asynchronous implementations are achieved by decreasing the amount of resources you use, and that needs to be baked into the implementation of an asynchronous method… it’s not something achieved by wrapping around it.

As an example, consider a synchronous method Sleep that doesn’t return for N milliseconds:

public void Sleep(int millisecondsTimeout)
{
    Thread.Sleep(millisecondsTimeout);
}

Now, consider the need to create an asynchronous version of this, such that the returned Task doesn’t complete for N milliseconds.  Here’s one possible implementation, simply wrapping Sleep with Task.Run to create a SleepAsync:

public Task SleepAsync(int millisecondsTimeout)
{
    return Task.Run(() => Sleep(millisecondsTimeout));
}

and here’s another that doesn’t use Sleep, instead rewriting the implementation to consume fewer resources:

public Task SleepAsync(int millisecondsTimeout)
{
    TaskCompletionSource<bool> tcs = null;
    var t = new Timer(delegate { tcs.TrySetResult(true); }, null, –1, -1);
    tcs = new TaskCompletionSource<bool>(t);
    t.Change(millisecondsTimeout, -1);
    return tcs.Task;
}

Both of these implementations provide the same basic behavior, both completing the returned task after the timeout has expired.  However, from a scalability perspective, the latter is much more scalable.  The former implementation consumes a thread from the thread pool for the duration of the wait time, whereas the latter simply relies on an efficient timer to signal the Task when the duration has expired.

Offloading

The ability to invoke a synchronous method asynchronously can be very useful for responsiveness, as it allows you to offload long-running operations to a different thread.  This isn’t about how many resources you consume, but rather is about which resources you consume.  For example, in a UI app, the specific thread handling pumping UI messages is “more valuable” for the user experience than are other threads, such as those in the ThreadPool.  So, asynchronously offloading the invocation of a method from the UI thread to a ThreadPool thread allows us to use the less valuable resources.  This kind of offloading does not require modification to the implementation of the operation being offloaded, such that the responsiveness benefits can be achieved via wrapping.

The ability to invoke a synchronous method asynchronously can also be very useful not just for changing threads, but more generally for escaping the current context.  For example, sometimes we need to invoke some user-provided code but we’re not in a good place to do it (or we’re not sure if we are).  Maybe a lock is held higher up the stack and we don’t want to invoke the user code while holding the lock.  Maybe we suspect we’re being invoked by some user code that doesn’t expect us to take a very long time. Rather than invoking the operation synchronously and as part of whatever is higher-up on the call stack, we can invoke the functionality asynchronously.

The ability to invoke a synchronous method asynchronously is also important for parallelism.  Parallel programming is all about taking a single problem and splitting it up into sub-problems that can each be processed concurrently.  If you were to split a problem into sub-problems but then process each sub-problem serially, you wouldn’t get any parallelism, as the entire problem would be processed on a single thread.  If, instead, you offload a sub-problem to another thread via asynchronous invocation, you can then process the sub-problems concurrently.  As with responsiveness, this kind of offloading does not require modification to the implementation of the operation being offloaded, such that parallelism benefits can be achieved via wrapping.

What does this have to do with my question?

Let’s get back to the core question: should we expose an asynchronous entry point for a method that’s actually synchronous?  The stance we’ve taken in .NET 4.5 with the Task-based Async Pattern is a staunch “no.”

Note that in my previous discussion of scalability and ofloading, I called out that the way to achieve scalability benefits is by modifying the actual implementation, whereas offloading can be achieved by wrapping and doesn’t require modifying the actual implementation.  That’s the key.  Wrapping a synchronous method with a simple asynchronous façade does not yield any scalability benefits.  And in such cases, by exposing only the synchronous method, you get some nice benefits, e.g.

  • Surface area of your library is reduced.  This means less cost to you (development, testing, maintenance, documentation, etc.).  It also means that your user’s choices are simplified.  While some choice is typically a good thing, too much choice often leads to lost productivity.  If I as a user am constantly faced with both a synchronous and an asynchronous method for the same operation, I constantly need to evaluate which of the pairs is the right one for me to use in each situation.
  • Your users will know whether there are actually scalability benefits to using exposed asynchronous APIs, since by definition then only APIs that benefit scalability are exposed asynchronously.
  • The choice of whether to invoke the synchronous method asynchronously is left up to the developer. Async wrappers around sync methods have overhead (e.g. allocating the object to represent the operation, context switches, synchronization around queues, etc.).  If, for example, your customer is writing a high-throughput server app, they don’t want to spend cycles on overhead that’s not actually benefiting them in any way, so they can just invoke the synchronous method.  If both the synchronous method and an asynchronous wrapper around it are exposed, the developer is then faced with thinking they should invoke the asynchronous version for scalability reasons, but in reality will actually be hurting their throughput by paying for the additional offloading overhead without the scalability benefits.

If a developer needs to achieve better scalability, they can use any async APIs exposed, and they don’t have to pay additional overhead for invoking a faux async API.  If a developer needs to achieve responsiveness or parallelism with synchronous APIs, they can simply wrap the invocation with a method like Task.Run.

The idea of exposing “async over sync” wrappers is also a very slippery slope, which taken to the extreme could result in every single method being exposed in both synchronous and asynchronous forms.  Many of the folks that ask me about this practice are considering exposing async wrappers for long-running CPU-bound operations.  The intention is a good one: help with responsiveness.  But as called out, responsiveness can easily be achieved by the consumer of the API, and the consumer can actually do so at the right level of chunkiness, rather than for each chatty individual operation.  Further, defining what operations could be long-running is surprisingly difficult.  The time complexity of many methods often varies significantly.

Consider, for example, a simple method like Dictionary<TKey,TValue>.Add(TKey,TValue).  This is a really fast method, right?  Typically, yes, but remember how dictionary works: it needs to hash the key in order to find the right bucket to put it into, and it needs to check for equality of the key with other entries already in the bucket.  Those hashing and equality checks can result in calls to user code, and who knows what those operations do or how long they take.  Should every method on dictionary have an asynchronous wrapper exposed? That’s obviously an extreme example, but there are simpler ones, like Regex.  The complexity of the regular expression pattern provided to Regex as well as the nature and size of the input string can have significant impact on the running time of matching with Regex, so much so that Regex now supports optional timeouts… should every method on Regex have an asynchronous equivalent?  I really hope not.

Guideline

This has all been a very long-winded way of saying that I believe the only asynchronous methods that should be exposed are those that have scalability benefits over their synchronous counterparts.  Asynchronous methods should not be exposed purely for the purpose of offloading: such benefits can easily be achieved by the consumer of synchronous methods using functionality specifically geared towards working with synchronous methods asynchronously, e.g. Task.Run.

Of course, there are exceptions to this, and you can witness a few such exceptions in .NET 4.5. 

For example, the abstract base Stream type provides ReadAsync and WriteAsync methods.  In most cases, derived Stream implementations work with data sources that aren’t in-memory, and thus involve disk I/O or network I/O of some kind.  As such, it’s very likely that derived implementations will be able to provide implementations of ReadAsync and WriteAsync that utilize asynchronous I/O rather than synchronous I/O that blocks threads, and thus there are scalability benefits to having ReadAsync and WriteAsync methods.  Further, we want to be able to work with these methods polymorphically, without regard for the concrete stream type, so we want to have these as virtual methods on the base class.  However, the base class doesn’t know how to implement these base implementations with asynchronous I/O, so the best it can do is provide asynchronous wrappers for the synchronous Read and Write methods (in actuality, ReadAsync and WriteAsync actually wrap BeginRead/EndRead and BeginWrite/EndWrite, respectively, which if not overridden will in turn wrap the synchronous Read and Write methods with an equivalent of Task.Run).

Another example in the same vein is TextReader, providing methods like ReadToEndAsync, which on the base class simply uses a Task to wrap an invocation of TextReader.ReadToEnd.  The expectation, however, is that the derived types developers actually use will override ReadToEndAsync to provide implementations that benefit scalability, such as StreamReader’s ReadToEndAsync method which utilizes Stream.ReadAsync.

Leave a Comment
  • Please add 1 and 1 and type the answer here:
  • Post
  • Richard, exactly.

  • This is a great post, thanks.  I'll definitely link people to it in the future.  The benefits of asynchrony can be confusing, so I'm sure that lots of devs will be creating async wrappers around sync methods once async/await becomes more familiar to them.  (There was an error saving my comment, so I'm trying again - FYI)

  • Thanks, Dave; I'm glad you find it helpful.

  • part 3 - I get errors when posting.

    I rewrote the timer sample from the scalalability section in VB. How do you stop the timer from the main method?

    Does the helper have to return a Task(Of Timer) in order to use a CancellationToken?

    Should the TaskCompletionSource offer a CancellationToken parameter?

    The IDE does not allow me to define the method Public Async Function Helper() As Timer, although the timer is defined in SleepAsync before the asynch operation happens. Thus SleepAsync could return it as out/byRef parameter.

    My personal feeling: Async/Await is a lot more complicated than EAP.

    thanks herbert

  • Part 4:    

    Sub Main()

           Console.WriteLine("Async Sleep Timer with cancel")

           Console.Title = "Async Sleep Timer with cancel"

           Call Helper()

           Threading.Thread.Sleep(3000)

           'ToDo: stop timer before it fires here *********************

           Console.WriteLine("Hit enter to exit after timer callback wrote 'Helper finished'.")

           Console.ReadLine()

       End Sub

       Public Async Sub Helper()

           'Async forbids ref or out parameters.

           Await SleepAsync(5000)

           Console.WriteLine("Helper finished!")

       End Sub

       Public Function SleepAsync(millisecondsTimeout As Integer) As Task

           Dim tcs As TaskCompletionSource(Of Boolean) = Nothing

           Dim t As New System.Threading.Timer(Sub() tcs.TrySetResult(True), Nothing, -1, -1)

           tcs = New TaskCompletionSource(Of Boolean)(t)

           t.Change(millisecondsTimeout, -1)                   'start timer

           Return tcs.Task

       End Function

  • herbertf, thanks for your interest.  Please post such questions in the forums at social.msdn.microsoft.com/.../threads.  The forums are meant more for such Q&As with longer dialogues, and more folks will be able to assist you there.

  • Thanks for the post. This is something that I've been wondering about. One issue that I ran into was that I was using BinaryReader/Writer inside what I wanted to convert into an async method, but, those classes in .NET 4.5 are still missing Async() equivalents to the synchronous ones that have always existed. I'm wondering if you know if there are plans to add asynchronous methods to these classes? I guess it might get a little ugly adding Async() versions of all the methods in those classes, but, it could be useful for allowing others to build async methods that make use of those classes. I figured it would be bad if I was using Task.Run() to run the synchronous version of those methods since they are very fine grained.

  • Jon: The reason that the BinaryReader/Writer do not have XxxAsync methods is that the methods on those types typically read/write only very few bytes from an underlying stream that has been previously opened. In practice, the data is frequently cached and the time required to fetch the data from the underlying source is typically so small that it is not worth it doing it asynchronously.

    Notably, there are some methods on these types that in some circumstances may transfer larger amounts of data (e.g. ReadString). Further down the line, Async versions for those methods may or may not be added, but it is unlikely it will happen in the immediate future.

    In general, you should only consider Async IO methods if the amount of data you are reading is significant (at least several hundreds or thousands of bytes), or if you are accessing a resource for the first time (e.g. a first read from a file may require to spin up the disk even if you are reading one byte).

  • Wow, this post helped me a lot.  After intensely studying CTP/TAP for the last 4 days (almost till my eyes bled, lol) I had a *lot* of questions about "when to do what" and when "not to", and these questions were not easily answered in all the technical writings. I would also like to say that CTP/TAP is a godsend for the application I'm currently working on. Hooray!

    Thanks Stephen!

  • Michael, I'm glad you found it helpful!

  • I am trying to do a simple example but I don't know hot to get it.

    I have a button in my WPF application that has the following code:

    private async void Button_Click_1(object sender, RoutedEventArgs e)

           {

               //Se ejecuta el método asíncrono

               bool miResultado = await miMetodoAync();

           }

    And I have my other method:

    private Task<bool> miMetodoAync()

           {

               TaskCompletionSource<bool> task = new TaskCompletionSource<bool>();

               Task t2 = task.Task;

               //My slow method

               for (Int64 i = 1; i < 1000000000000; i++)

               {

                   string hola = "hola";

                   hola = hola + " hola dos veces";

               }

               task.SetResult(true);

               return task.Task;

           }

    If I use this code, my UI is blocked.

    How should I implement this code to use async to not block de UI and not use an extra thread?

    Thanks.

  • @ComptonAlvaro:

    You could just change:

       bool miResultado = await miMetodoAsync();

    to:

       bool miResultado = await Task.Run(() => miMetodoAsync());

  • Hello Stephen:

    Well if I understad correctly, I see three different ways to do it (I know that some of them could be wrong).

                 NOTE: I divide the post in two, because the max characteres are 3072.

    //This is my click event, and I don't that this code block the UI

    SOLUTION 1:

    private async void Button_Click_1(object sender, RoutedEventArgs e)

          {

              //execute the async method

              bool miResultado = await Task.Run(() => miMetodoAsync());

          }

    private Task<bool> miMetodoAsync()

          {

              TaskCompletionSource<bool> task = new TaskCompletionSource<bool>();

              Task t2 = task.Task;

              //My slow method

              Int64 resultado = 0;

              for (Int64 i = 1; i < 1000000000000; i++)

              {

                  resultado = resultado + 1;

              }

              task.SetResult(true);

              return task.Task;

          }

    But in this way, how I use in my click event a Task to execute miMetodoAsync, I am creating a new thread, and if I am not wrong, this is what we want to avoid, use more resources.

  • SOLUTION 2:

    How I am creating in the solution 1 a new task, a new thread using task.Run, why to use a method that returns a Task? is not the same this way?

    //This is my click event, and I don't that this code block the UI

    private async void Button_Click_1(object sender, RoutedEventArgs e)

          {

              //execute the async method

              bool miResultado = await Task.Run(() => miMetodo());

          }

    private bool miMetodo()

          {

              //My slow method

              Int64 resultado = 0;

              for (Int64 i = 1; i < 1000000000000; i++)

              {

                  resultado = resultado + 1;

              }

              return true;

          }

    Instead of using a async method, use a sync method and excute it inside a task in the click event. Which is the difference? Which is the best option?

    Other question that I have is if in my async method, I am using TaskCompletionSource. I mean, that I use:

         Task t2 = task.Task;

    is this needed? or just creating a TaskCompletionSource and set the result is enough?

    If the best option is to use a async method that returns a Task, could I use this way?

    SOLUTION 3:

    //This is my click event, and I don't that this code block the UI

    private async void Button_Click_1(object sender, RoutedEventArgs e)

          {

              //execute the async method

              bool miResultado = await miMetodoAsync;

          }

    private async Task<bool> miMetodoAsync()

          {

              await Task.Run(()=>

                               {

                                     //my slow code;

                                     return true;

                                });

          }

    In this way, It is not needed to use a task every time that I call my async method, I create the task in the async method, so I think the code is more clearly (perhaps internally the compile result is different). However in this way I think that I wraping a sync method in a async method, in the same way that you explain in the first example of your article, the wrong way. So in the practice, in this solution 3 I could use in my slow code in miMetodoAsync() a sync method.

    Which is the difference between the solution 1 and the solution 3? I think that the solution 2 is the worst way, by why? Really I don't see differences if I need to use a task.Run in my click event, because is used a new thread.

    Thank you very much.

    Alvaro.

  • PD: the solution 2 perhaps does not work because although I execute the method in a task, the method need return a task to be able to use the await keyword in the click event method. is this correct?

    Thanks.

    Alvaro.

Page 2 of 3 (34 items) 123