Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
297 views
in Technique[技术] by (71.8m points)

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

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

The problem turned out to be because a Winforms UI Control was accidentally being created before the Task was started.

Here is an example that reproduces it. You will see that if you run the test as-is, it passes. If you run the test with the Form line uncommented, it fails.

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        // Create new sync context for unit test
        SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());

        var waitHandle = new ManualResetEvent(false);

        var doer = new DoSomethinger();

        //Uncommenting this line causes the ContinueWith part of the Task
        //below never to execute.
        //var f = new Form();

        doer.DoSomethingAsync(() => waitHandle.Set());

        Assert.IsTrue(waitHandle.WaitOne(10000), "Wait timeout exceeded.");
    }
}


public class DoSomethinger
{
    public void DoSomethingAsync(Action onCompleted)
    {
        var task = Task.Factory.StartNew(() => Thread.Sleep(1000));

        task.ContinueWith(t =>
        {
            if (onCompleted != null)
                onCompleted();

        }, TaskScheduler.FromCurrentSynchronizationContext());
    }
}

Can anyone explain why this is the case?

I thought it might have been because the wrong SynchronizationContext is used, but actually, the ContinueWith never executes at all! And besides, in this unit test, whether or not it is the correct SynchronizationContext is irrelevant because as long as the waitHandle.set() is called on any thread, the test should pass.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...