15

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?

Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
  • 1
    *Why* are you *blocking* on the already asynchronous call? This isn't a "sync wrapper", it's a blocking call – Panagiotis Kanavos Jul 11 '17 at 12:25
  • 2
    Why do you have a ton of these wrappers? If you're a library author, you should only expose the real operations and leave it up to your callers to "switch" between async and sync if they need to. – Damien_The_Unbeliever Jul 11 '17 at 12:25
  • @Damien_The_Unbeliever i'm going to remove this sync wrapper, but I have to know if I can remove this on client side where it's not a simple wrapper, but task but be awaited synchronously anyway. I know about `Do not expose sync as wrappers of async and vice versa`, so i'm going to remove them. But it's anyway beside the point. Question may be reformulated as `how correctly syncrounosly await task results` – Alex Zhukovskiy Jul 11 '17 at 12:28
  • What doesn't make sense is `GetAwaiter().GetResult()`. If you write a library *and* `async/await`, use `ConfigureAwait(false)` and leave it to the caller to decide how to `await`. If the client wants to block on the asynchronous calls, that's their problem (and it is a problem) – Panagiotis Kanavos Jul 11 '17 at 12:28
  • 1
    Why should the client have to *block* on the task at all? Even console applications have `async Task Main(..)` in C# 7.1. Even in previous versions, you can keep almost all of your code `async` inside a `MainAsync` method and use `MainAsync().GetAwaiter().GetResult()` to block until processing completes – Panagiotis Kanavos Jul 11 '17 at 12:30
  • @PanagiotisKanavos 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. I don't say about how many call sites have to be updated acoordingly. And, as I said, it's anyway beside the point. We have tons of situations where we have to wait until tasks ends, for example if you write some handler for external framework that doesn't provide async overrides. I like your solution to question `how to block` - `don't`, but it doesn't fit here. – Alex Zhukovskiy Jul 11 '17 at 12:35
  • 2
    Apparently the correct way is just to use .GetAwaiter().GetResult(). See https://www.infoq.com/news/2017/06/CSharp-7.1-a – Phil Jul 12 '17 at 12:11
  • @Fabio I don't use `async/await`. Why did you even mention it? – Alex Zhukovskiy Jul 12 '17 at 12:56
  • @Phil could you post it as answer because it's just what I was asking for. – Alex Zhukovskiy Jul 12 '17 at 12:56
  • After asynchronous method completes - by default execution will try to continue on the same context where it started. Where by setting `.ConfigureAwait(false)` you allow execution continues with the context asynchronous was completed with(it can be another thread). So if you have context-specific code after blocked asynchronous method (UI for example) - exception will be thrown – Fabio Jul 12 '17 at 13:52
  • @Fabio did you even were using tasks before C# 5.0 and async/await came? I mean it has nothing to do with async/await, it's purely about tasks. – Alex Zhukovskiy Jul 25 '17 at 12:31
  • 1
    `async-await` is just compiler feature/wrapper for asynchronous tasks. So same rules applies there too. If you start "re-writing" software to be asynchronus, then maybe starting from top to bottom will be little bid easier, because you can just wrap existing lower layer code with already completed task and return it. Then move step lower and implement asynchronous method with "correct" asynchronous implementation. – Fabio Jul 25 '17 at 13:16

1 Answers1

17

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 3
    More hacks available [here](https://msdn.microsoft.com/en-us/magazine/mt238404.aspx). The reply I always give to this question is: "there are various hacks, but each one has drawbacks, and none of them work in all scenarios". – Stephen Cleary Jul 12 '17 at 14:13
  • @StephenCleary: Thanks for the link; I hadn't read that article yet. Looks great! – Eric Lippert Jul 12 '17 at 14:24
  • 1
    @EricLippert nice answer. However, I think it's not always possible. I'm working on moving from sync to async, but I'm doing it continiously. For example, today I fixed 10 classes on the bottom level, tomorrow I will fix 10 classes that were calling these and so on.But each day I have to mark a line where sync code is blocking on async operation. In several iterations I can come to the top level and change controllers/event handlers/... to be async and here my story ends.The problem is that I just can't do it in one single effort, no one will examine thousands changes in just one single review – Alex Zhukovskiy Jul 13 '17 at 15:44
  • @StephenCleary thank you for this article, it just what I was needed for my `step-by-step` work. – Alex Zhukovskiy Jul 13 '17 at 15:52
  • 2
    But what if the third bullet in the bad scenario never happens? What if you block on a Task in a manually created thread? "Blocking is wrong" sounds somewhat extreme. – Joker_vD Jul 18 '17 at 12:20
  • Your question is: if nothing bad happens does something bad happen? No. If I run this red light but there are never any consequences then are there consequences? No. – Eric Lippert Jul 18 '17 at 13:10