0

I'm making some tests with async await since one of my unit tests was not returning at all, unless i would add a ConfigureAwait(false); to one of the involved tasks. Well I decided to find out what exactly is happening there and I wrote the following test. I'm not sure why, but the anonymous call (3.) blocks, while 1 & 2 do not.

I'm trying to check the differences with ilspy but I think it would be interesting to get some opinions on this behavior. I'm running on vs 2017 .net 4.5.2. I know that async void is bad... this is not the point here.

[Edit] I have added some more details so you can see there is no SyncContext at all at the beginning as this is a CONSOLE app. Besides, this kind of problem has been posted long time ago here: https://blog.stephencleary.com/2012/02/async-console-programs.html

internal class Program
{
    private static void Main(string[] args)
    {
        //System.Console.WriteLine($"Main dispatcher: {SynchronizationContext.Current?.GetHashCode()}");
        //// 1. Method call - no blocking
        //var mainTask = Task.Run(() => DoTheSame());
        //mainTask.Wait();

        // 2. Anonymous + Thread - no block
        //var mainThread = new Thread(
        //async () =>
        //{
        //    System.Console.WriteLine($"Before setting dispatcher: {SynchronizationContext.Current?.GetHashCode()}");
        //    SynchronizationContext.SetSynchronizationContext(
        //                    new DispatcherSynchronizationContext(
        //                    Dispatcher.CurrentDispatcher));
        //    System.Console.WriteLine($"After setting dispatcher: {SynchronizationContext.Current?.GetHashCode()}");

        //    var ts = new TaskCompletionSource<bool>();

        //    await Task.Run(() =>
        //    {
        //        ts.SetResult(true);
        //    });

        //    await ts.Task;
        //});
        //mainThread.Start();
        //mainThread.Join();

        // 3. Anonymous + Task - blocks
        var mainTask = Task.Run(
        async () =>
        {
            System.Console.WriteLine($"Before setting dispatcher: {SynchronizationContext.Current?.GetHashCode()}"); // null of course
            SynchronizationContext.SetSynchronizationContext(
                            new DispatcherSynchronizationContext(
                            Dispatcher.CurrentDispatcher));
            System.Console.WriteLine($"After setting dispatcher: {SynchronizationContext.Current?.GetHashCode()}");
            var ts = new TaskCompletionSource<bool>();
            await Task.Run(() =>
            {
                ts.SetResult(true);
            });

            // using .ConfigureAwait(false); will make it work

            await ts.Task;
        });

        mainTask.Wait();
    }

    private static async void DoTheSame()
    {
        System.Console.WriteLine($"Before setting dispatcher: {SynchronizationContext.Current?.GetHashCode()}");
        SynchronizationContext.SetSynchronizationContext(
                        new DispatcherSynchronizationContext(
                        Dispatcher.CurrentDispatcher));
        System.Console.WriteLine($"After setting dispatcher: {SynchronizationContext.Current?.GetHashCode()}");

        var ts = new TaskCompletionSource<bool>();

        await Task.Run(() =>
        {
            ts.SetResult(true);
        });

        await ts.Task;
    }
}
Marco
  • 984
  • 10
  • 18
  • Well, without reading 1 & 2, I can tell you that accessing `Dispatcher.CurrentDispatcher` on a worker thread like you are inside `Task.Run` in 3 will create a _new_ dispatcher, which isn’t actually running. So when you post to the sync context, the posted action will never execute, because nothing is processing that dispatcher’s queue. You need to capture the dispatcher while you’re actually running on the UI thread. – Mike Strobel Feb 15 '18 at 23:30
  • Note that this doesn’t actually explain the blocking. My point is, whatever you _think_ that sync context is accomplishing, it actually isn’t. It’s only serving to confuse you further. – Mike Strobel Feb 15 '18 at 23:39
  • 1
    @Mike: _"this doesn’t actually explain the blocking"_ -- doesn't it? As you already noted: _"accessing Dispatcher.CurrentDispatcher on a worker thread like you are inside Task.Run in 3 will create a new dispatcher, which isn’t actually running"_ -- since the dispatcher isn't running, it has no way to execute the continuation. So it never happens. And that's why using `ConfigureAwait(false)` "fixes" it, because that says to ignore the current context, so the continuation can happen on an arbitrary thread pool thread. – Peter Duniho Feb 16 '18 at 00:55
  • @Mike Please note that this is a console application, there is no GUI thread running at all. The answers still do not explain why calling the method works.The https://stackoverflow.com/questions/31713263/why-did-dispatcher-begininvoke-fail-where-control-begininvoke-succeed-in-c-sharp is different as he has a GUI thread running already. Also, I use this on my COM based c# where i'm running a own GUI thread without any problems, no blocking at all. If you would be right, 1. shouldn't work as well. However you might have a point there. Anyway, i will check the generated il. – Marco Feb 16 '18 at 08:06
  • @Peter: I see only a very slight similarity to the question you mentioned. On my case A. There is no control involved, B. there is no Gui thread running (It's a console app) C. The answers certainly do not explain why 1. does not block. – Marco Feb 16 '18 at 08:15
  • _"is different as he has a GUI thread running already"_ -- but the problem there, as here, is that he's not using it. He could, but doesn't. You don't even have the option. That said, if you want to keep looking for some other explanation even when it's right in front of you, be my guest. – Peter Duniho Feb 16 '18 at 08:38
  • @Peter: Got your point. But why does 1. works? – Marco Feb 16 '18 at 09:39
  • yes, i see now. You are right. No matter which method i use 1, 2 or 3 the task running ts.SetResult(true); never comes out. The dispatcher gets a post but it never starts the task (await ts.Task;) Thanks! – Marco Feb 16 '18 at 10:04

0 Answers0