Why does the Task.ContinueWith fail to execute in this Unit Test?

前端 未结 2 1172
深忆病人
深忆病人 2020-12-17 19:45

I have come across a problem with a unit test that failed because a TPL Task never executed its ContinueWith(x, TaskScheduler.FromCurrentSynchronizationContext())

相关标签:
2条回答
  • 2020-12-17 19:56

    I overlooked the comments section in your code, Indeed that fails when uncommenting the var f = new Form();

    Reason is subtle, Control class will automatically overwrite the synchronization context to WindowsFormsSynchronizationContext if it sees that SynchronizationContext.Current is null or its is of type System.Threading.SynchronizationContext.

    As soon as Control class overwrite the SynchronizationContext.Current with WindowsFormsSynchronizationContext, all the calls to Send and Post expects the windows message loop to be running in order to work. That's not going to happen till you created the Handle and you run a message loop.

    Relevant part of the problematic code:

    internal Control(bool autoInstallSyncContext)
    {
        ...
        if (autoInstallSyncContext)
        {
           //This overwrites your SynchronizationContext
            WindowsFormsSynchronizationContext.InstallIfNeeded();
        }
    }
    

    You can refer the source of WindowsFormsSynchronizationContext.InstallIfNeeded here.

    If you want to overwrite the SynchronizationContext, you need your custom implementation of SynchronizationContext to make it work.

    Workaround:

    internal class MyContext : SynchronizationContext
    {
    
    }
    
    [TestMethod]
    public void TestMethod1()
    {
        // Create new sync context for unit test
        SynchronizationContext.SetSynchronizationContext(new MyContext());
    
        var waitHandle = new ManualResetEvent(false);
    
        var doer = new DoSomethinger();
        var f = new Form();
    
        doer.DoSomethingAsync(() => waitHandle.Set());
    
        Assert.IsTrue(waitHandle.WaitOne(10000), "Wait timeout exceeded.");
    }
    

    Above code works as expected :)

    Alternatively you could set WindowsFormsSynchronizationContext.AutoInstall to false, that will prevent automatic overwriting of the synchronization context mentioned above.(Thanks for OP @OffHeGoes for mentioning this in comments)

    0 讨论(0)
  • 2020-12-17 19:56

    With the line commented out, your SynchronizationContext is the default one you created. This will cause TaskScheduler.FromCurrentSynchrozisationContext() to use the default scheduler, which will run the continuation on the thread pool.

    Once you create a Winforms object like your Form, the current SynchronizationContext becomes a WindowsFormsSynchronizationContext, which in turn will return a scheduler that depends on the WinForms message pump to schedule the continuation.

    Since there is no WinForms pump in a unit test, the continuation never gets run.

    0 讨论(0)
提交回复
热议问题