3

C# offers multiple ways to perform asynchronous execution such as threads, futures, and async.

In what cases is async the best choice?

I have read many articles about the how and what of async, but so far I have not seen any article that discusses the why.

Initially I thought async was a built-in mechanism to create a future. Something like

async int foo(){ return ..complex operation..; }
var x = await foo();
do_something_else();
bar(x);

Where call to 'await foo' would return immediately, and the use of 'x' would wait on the the return value of 'foo'. async does not do this. If you want this behavior you can use the futures library: https://msdn.microsoft.com/en-us/library/Ff963556.aspx

The above example would instead be something like

int foo(){ return ..complex operation..; }
var x = Task.Factory.StartNew<int>(() => foo());
do_something_else();
bar(x.Result);

Which isn't as pretty as I would have hoped, but it works nonetheless.

So if you have a problem where you want to have multiple threads operate on the work then use futures or one of the parallel operations, such as Parallel.For.

async/await, then, is probably not meant for the use case of performing work in parallel to increase throughput.

jonr
  • 1,133
  • 1
  • 11
  • 25
  • Maybe see http://blog.teamleadnet.com/2013/08/async-await-in-net-is-not-for.html – user2864740 Sep 02 '15 at 02:06
  • In particular http://stackoverflow.com/a/24028346/477420 answer in linked duplicate covers the same areas as good self-answered post here. – Alexei Levenkov Sep 02 '15 at 02:19
  • 1
    I don't think this question is a duplicate of that link, Alexei. That link asks the question 'how does async work'. I clearly say I am not interested in the how or what of async, but rather the why. I go into a little detail about the how for async in an attempt to explain the why, but I try to mostly answer the why. – jonr Sep 02 '15 at 06:33

1 Answers1

15

async solves the problem of scaling an application for a large number of asynchronous events, such as I/O, when creating many threads is expensive.

Imagine a web server where requests are processed immediately as they come in. The processing happens on a single thread where every function call is synchronous. To fully process a thread might take a few seconds, which means that an entire thread is consumed until the processing is complete.

A naive approach to server programming is to spawn a new thread for each request. In this way it does not matter how long each thread takes to complete because no thread will block any other. The problem with this approach is that threads are not cheap. The underlying operating system can only create so many threads before running out of memory, or some other kind of resource. A web server that uses 1 thread per request will probably not be able to scale past a few hundred/thousand requests per second. The c10k challenge asks that modern servers be able to scale to 10,000 simultaneous users. http://www.kegel.com/c10k.html

A better approach is to use a thread pool where the number of threads in existence is more or less fixed (or at least, does not expand past some tolerable maximum). In that scenario only a fixed number of threads are available for processing the incoming requests. If there are more requests than there are threads available for processing then some requests must wait. If a thread is processing a request and has to wait on a long running I/O process then effectively the thread is not being utilized to its fullest extent, and the server throughput will be much less than it otherwise could be.

The question is now, how can we have a fixed number of threads but still use them efficiently? One answer is to 'cut up' the program logic so that when a thread would normally wait on an I/O process, instead it will start the I/O process but immediately become free for any other task that wants to execute. The part of the program that was going to execute after the I/O will be stored in a thing that knows how to keep executing later on.

For example, the original synchronous code might look like

void process(){
   string name = get_user_name();
   string address = look_up_address(name);
   string tax_forms = find_tax_form(address);
   render_tax_form(name, address, tax_forms);
}

Where look_up_address and find_tax_form have to talk to a database and/or make requests to other websites.

The asynchronous version might look like

void process(){
  string name = get_user_name();
  invoke_after(() => look_up_address(name), (address) => {
     invoke_after(() => find_tax_form(address), (tax_forms) => {
        render_tax_form(name, address, tax_forms);
     }
  }
}

This is continuation passing style, where next thing to do is passed as the second lambda to a function that will not block the current thread when the blocking operation (in the first lambda) is invoked. This works but it quickly becomes very ugly and hard to follow the program logic.

What the programmer has manually done in splitting up their program can be automatically done by async/await. Any time there is a call to an I/O function the program can mark that function call with await to inform the caller of the program that it can continue to do other things instead of just waiting.

async void process(){
  string name = get_user_name();
  string address = await look_up_address(name);
  string tax_forms = await find_tax_form(address);
  render_tax_form(name, address, tax_forms);
}

The thread that executes process will break out of the function when it gets to look_up_address and continue to do other work: such as processing other requests. When look_up_address has completed and process is ready to continue, some thread (or the same thread) will pick up where the last thread left off and execute the next line find_tax_forms(address).

Since my current belief of async is about managing threads, I don't believe that async makes a lot of sense for UI programming. Generally UI's will not have that many simultaneous events that need to be processed. The use case for async with UI's is preventing the UI thread from being blocked. Even though async can be used with a UI, I would find it dangerous because ommitting an await on some long running function, due to either an accident or forgetfulness, would cause the UI to block.

async void button_callback(){
   await do_something_long();
   ....
}

This code won't block the UI because it uses an await for the long running function that it invokes. If later on another function call is added

async void button_callback(){
   do_another_thing();
   await do_something_long();
   ...
}

Where it wasn't clear to the programmer who added the call to do_another_thing just how long it would take to execute, the UI will now be blocked. It seems safer to just always execute all processing in a background thread.

void button_callback(){
  new Thread(){
    do_another_thing();
    do_something_long();
    ....
  }.start();
}

Now there is no possibility that the UI thread will be blocked, and the chances that too many threads will be created is very small.

jonr
  • 1,133
  • 1
  • 11
  • 25
  • Another alternative to processing async code (from async/awit or CPS) include a stream model like Rx. The benefit of using (or 'sticking with') async/wait when compared to something like CPS/Rx is that it's still allows a linear/procedural program flow much like futures. – user2864740 Sep 02 '15 at 02:02
  • new `Thread(...).Start()` is not the recommended way to start up a thread, IMHO. Use `Task.Run` or use `Task.Factory.StartNew(..., TaskCreationOptions.LongRunning, TaskScheduler.Default)` if you need a non thread-pool thread. – Scott Chamberlain Sep 02 '15 at 02:36
  • As far now, best answer explaining the meaning and use cases of asynchronous processing in web servers. Thanks!! – proprius Dec 02 '16 at 06:59
  • Nive explanation but it actually only explains aysnc programming and not the language construct of async/await. – Code Spirit Jan 22 '20 at 08:50