Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly block on async code?

I have tons of code written in following manner:

public string SomeSyncOperation(int someArg)
{
   // sync code 
   SomeAsyncOperation(someArg, someOtherArg).ConfigureAwait(false).GetAwaiter().GetResult()
   // sync code
};

Here we have some sync code that have to access to async api, so it blocks until results are ready. We can't method change signature and add async here. So, we are waiting synchronously anyway, so do we need ConfigureAwait(false) here? I'm pretty sure that we don't, but I'm a bit affraid of removing it because it's probably covers some use cases (or why am I seeing it virtually everywhere? It's just a cargo cult?) and removing this call may lead to some unsafe results.

So does it makes sense at all?

like image 773
Alex Zhukovskiy Avatar asked Jul 11 '17 12:07

Alex Zhukovskiy


People also ask

Can async be blocking?

Async is widely considered to be non-blocking.

Do async functions block?

In async functions, await blocks any code that follows from executing until the Promise has resolves, which means that our refactored code doesn't even start asyncThing2() until asyncThing1() has completed — that's not good.

How do I stop async calls?

You can cancel an asynchronous operation after a period of time by using the CancellationTokenSource. CancelAfter method if you don't want to wait for the operation to finish.

Does async await block the thread?

Because await is only valid inside async functions and modules, which themselves are asynchronous and return promises, the await expression never blocks the main thread and only defers execution of code that actually depends on the result, i.e. anything after the await expression.


1 Answers

How to correctly block on async code?

You do not correctly block on async code. Blocking is wrong. Asking what the right way is to do the wrong thing is a non-starter.

Blocking on async code is wrong because of the following scenario:

  • I have an object in hand representing an async operation.
  • The async operation is itself asynchronously waiting on the completion of a second async operation.
  • The second async operation will be scheduled to this thread when the message loop executes code associated with a message that is at present in this thread's message queue.

And now you can figure out what goes horribly wrong when you attempt to fetch the result synchronously of the first async operation. It blocks until its child async operation is finished, which will never happen, because now we've blocked the thread that is going to service the request in the future!

Your choices are:

  1. Make your entire call stack correctly asynchronous and await the result.
  2. Don't use this API. Write an equivalent synchronous API that you know does not deadlock, from scratch, and call it correctly.
  3. Write an incorrect program which sometimes deadlocks unpredictably.

There are two ways to write a correct program; writing a synchronous wrapper over an asynchronous function is dangerous and wrong.

Now, you might ask, didn't the ConfigureAwait solve the problem by removing the requirement that we resume on the current context? That's not the resumption point that we're worried about. If you're going to rely on ConfigureAwait to save you from deadlock then every asynchronous operation in the stack has to use it, and we don't know if the underlying asynchronous operation that is about to cause the deadlock did that!

If the above is not entirely clear to you, read Stephen's article on why this is a bad practice, and why common workarounds are just dangerous hacks.

https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

and his updated article giving more hacks and workarounds here:

https://msdn.microsoft.com/en-us/magazine/mt238404.aspx?f=255&MSPPError=-2147217396

But again: the right thing to do is to redesign your program to embrace asynchrony and use await throughout. Don't try to work around it.

becuase this method has stacktrace of ~20 methods, some of them are implementing some interfaces. Changing it to be async require change declarations in ~50 files, and we convert fully sync interfaces to mixed ones.

Get busy then! This sounds pretty easy.

like image 77
Eric Lippert Avatar answered Sep 28 '22 08:09

Eric Lippert