3

I wrote a simple console application to test ConfigureAwait(false) for fixing the problem of calling an async function in a synchronized way. This is how I simulate an event and trying to call an async function in a sync way.

 public static class Test2
    {
        private static EventHandler<int> _somethingEvent;

        public static void Run()
        {
            _somethingEvent += OnSomething;
            _somethingEvent(null, 10);
            Console.WriteLine("fetch some other resources");
            Console.ReadLine();
        }

        private static void OnSomething(object sender, int e)
        {
            Console.WriteLine($"passed value : {e}");
            FakeAsync().Wait();
        }

        private static async Task FakeAsync()
        {
            await Task.Delay(2000).ConfigureAwait(false);
            Console.WriteLine($"task completed");
        }
    }

the output of the code is like this:

passed value : 10
task completed
fetch some other resources

And when I change the OnSomething function to async void like this:

private static async void OnSomething(object sender, int e)
{
    Console.WriteLine($"passed value : {e}");
    await FakeAsync();
}

The output will change to this:

passed value : 10
fetch some other resources
task completed

You can see fetch some other resources is occurred before task completed section and this is my desired result.
My question is how can I achieve the same result by calling the async function synchronized like the first signature I wrote for the OnSomething function?
How could I use ConfigureAwait(false) to help me in this situation? Or maybe I am not using it properly.

Milad Bonakdar
  • 273
  • 2
  • 16
  • Not sure if I understand entirely so just making a simple comment: you can wait for a certain amount of tasks to be completed before proceeding to executing further code. You do this by creating the tasks and then awaiting Task.WhenAll(task 1, task 2, ...). – Jonast92 Feb 18 '20 at 13:48
  • 1
    Yeah, I know that. but my problem is not like that. My event will happen at any time and I don't know when. I just want my sync calling to act like an async one. https://www.youtube.com/watch?v=avhLfcgIwbg – Milad Bonakdar Feb 18 '20 at 13:58
  • 2
    You say that you want your synchronous wait to act like an asynchronous wait. Synchronous and asynchronous are **opposites**. The way to make a wait that acts like an asynchronous wait is to make an asynchronous wait, not a synchronous wait. – Eric Lippert Feb 18 '20 at 14:30
  • 2
    Think about it this way. You put some bread in a toaster. You have two choices. You can stand by the toaster doing nothing else until the toast pops out -- that's a synchronous wait. Or you can go check your mailbox to see if any packages have arrived, and when you're done checking, you check the toaster to see if the toast popped. That's an asynchronous wait. Please describe for me what you mean in this analogy by doing a synchronous wait that acts asynchronous. Understanding what people really want from this feature helps me design better features. – Eric Lippert Feb 18 '20 at 14:33
  • I was thinking if I use *ConfigureAwait(false)* the following code will be run by the background thread and the main thread is free to go back do what it was doing. – Milad Bonakdar Feb 18 '20 at 14:40

2 Answers2

4

I wrote a simple console application to test ConfigureAwait(false) for fixing the problem of calling an async function in a synchronized way.

There's a couple of problems with this.

  1. ConfigureAwait(false) is not a "fix" for the "problem" of calling an asynchronous function synchronously. It is one possible hack that works in some situations, but it's not generally recommended because you have to use ConfigureAwait(false) everywhere in the entire transitive closure of that function - including second- and third-party calls - and if you just miss one, there is still the possibility of a deadlock. The best solution for the problem of calling an asynchronous function from a synchronous function is to change the calling function to be asynchronous (go "async all the way"). Sometimes this isn't possible and you need a hack, but there is no hack that works in every scenario.
  2. The deadlock issue you're trying to avoid has two ingredients: blocking a thread on a task, and a single-threaded context. Console applications do not have a single-threaded context, so in a Console application it's as though all your code uses ConfigureAwait(false) everywhere.

My question is how can I achieve the same result by calling the async function synchronized like the first signature I wrote for the OnSomething function?

The calling code will either block on the asynchronous code (e.g., Wait), or it will not (e.g., await). You can't block on the asynchronous code and continue executing. The calling thread must either continue executing or block; it can't do both.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • To solve the problem of calling an asynchronous function synchronously, is it possible to schedule a timer that periodically checks the state of the task and perform the continuation when the task is completed? Actively monitoring Task.IsCompleted seems to resolve this problem. See [Eric's answer](https://stackoverflow.com/a/60079843/1846281) to know where I got this idea. – Luca Cremonesi Feb 24 '20 at 15:49
  • @LucaCremonesi: Your code either blocks a thread or it doesn't. If the thread is blocked, it can deadlock. If the thread is not blocked, then `await` would be a much simpler solution than a timer. – Stephen Cleary Feb 25 '20 at 20:52
  • If my code potentially blocks a thread, I can schedule a timer in the future when the task is completed. In fact, according to Eric Lippert, "If you already know that the task is complete then it is safe to synchronously wait for the result.". At that time we can call `task.Result` without causing any possible deadlock. Is this correct? – Luca Cremonesi Feb 27 '20 at 16:47
  • Yes, that is technically correct. You *can* start a task on a background thread and also start a timer that polls for the completion of that task. But it's waaaay easier to just do `await Task.Run(..);` - which starts a task on a background thread and then schedules a continuation to run immediately when the task is completed. – Stephen Cleary Feb 28 '20 at 02:27
  • Thank you, Stephen, for your explanation. So, basically, you are referring to the "solution C" of your [answer](https://stackoverflow.com/a/9343733/1846281) about calling an asynchronous function in a synchronous method. That's much easier than starting a timer and manually checking the state of the task. If your asynchronous function correctly works in a thread pool context, that should be the best solution to the problem. – Luca Cremonesi Feb 28 '20 at 16:09
  • 1
    @LucaCremonesi: Don't lose sight of the fact that the best solution is always to go async all the way. Only block if absolutely necessary. – Stephen Cleary Feb 28 '20 at 19:55
1

It's because when you call _somethingEvent(null, 10);, OnSomething isn't being awaited, so the following Console.WriteLine("fetch some other resources"); will be allowed to run straight after await FakeAsync(); is executed.

If you don't need to await the completion of OnSomething before continuing, just do a fire and forget:

private static void OnSomething(object sender, int e)
{
    Console.WriteLine($"passed value : {e}");
    Task.Run(() => FakeAsync());
}
Johnathan Barclay
  • 18,599
  • 1
  • 22
  • 35