Task.ContinueWith and DispatcherSynchronizationCon

2019-08-28 01:42发布

问题:

I'm facing an issue that I do not understand when unit testing a code that uses task continuation and DispatcherSynchrinizationContext.

My unit test code :

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext());

        var class1 = new Class1();
        var result = class1.MyAsyncMethod().Result;

        Assert.IsTrue(result == "OK");
    }
}

The code that is being tested :

class Class1
{
    public Task<string> MyAsyncMethod()
    {
        var tcs = new TaskCompletionSource<string>();

        MyInnerAsyncMethod()
            .ContinueWith(t =>
            {
                // Never reached if TaskScheduler.FromCurrentSynchronizationContext() is set
                tcs.SetResult("OK");

            }, TaskScheduler.FromCurrentSynchronizationContext());

        return tcs.Task;
    }


    private Task<string> MyInnerAsyncMethod()
    {
        var tcs = new TaskCompletionSource<string>();
        tcs.SetResult("OK");
        return tcs.Task;
    }
}

The problem is that the code contained within the "ContinueWith" method is never reached IF I specify "TaskScheduler.FromCurrentSynchronizationContext()". If I remove this parameter, the continuation executes correctly ...

Any ideas or advices?

回答1:

I think this is because although you have created a new DispatcherSynchronisationContext, there is no actual thread running a dispatch loop for your task to execute on.

Try putting this at the beginning of your test:

// Create a thread
Thread newWindowThread = new Thread(new ThreadStart( () =>
{
    // Create our context, and install it:
    SynchronizationContext.SetSynchronizationContext(
        new DispatcherSynchronizationContext(
            Dispatcher.CurrentDispatcher));

    // Start the Dispatcher Processing
    System.Windows.Threading.Dispatcher.Run();
}));

Courtesy of: http://reedcopsey.com/2011/11/28/launching-a-wpf-window-in-a-separate-thread-part-1/



回答2:

lain you put me on the right way, thank you !!

I did not modify my code being tested and here is a new implementation of the test code that works as expected :

[TestClass]
public class UnitTest1
{
    private ExecutionContext _executionContext;

    [TestInitialize]
    public void OnSetup()
    {
        _executionContext = CreateExecutionContext();

        SynchronizationContext.SetSynchronizationContext(_executionContext.DispatcherSynchronizationContext);
    }

    [TestCleanup]
    public void OnTearDown()
    {
        // stops the dispatcher loop
        _executionContext.Dispatcher.InvokeShutdown();
    }

    [TestMethod]
    public void TestMethod1()
    {
        var class1 = new Class1();
        var result = class1.MyAsyncMethod().Result;

        Assert.IsTrue(result == "OK");
    }

    /* Helper classes and methods */

    private ExecutionContext CreateExecutionContext()
    {
        var tcs = new TaskCompletionSource<ExecutionContext>();

        var mockUIThread = new Thread(() =>
                {
                    // Create the context, and install it:
                    var dispatcher = Dispatcher.CurrentDispatcher;
                    var syncContext = new DispatcherSynchronizationContext(dispatcher);

                    SynchronizationContext.SetSynchronizationContext(syncContext);

                    tcs.SetResult(new ExecutionContext
                        {
                            DispatcherSynchronizationContext = syncContext, 
                            Dispatcher = dispatcher
                        });

                    // Start the Dispatcher Processing
                    Dispatcher.Run();
                });

        mockUIThread.SetApartmentState(ApartmentState.STA);
        mockUIThread.Start();

        return tcs.Task.Result;
    }

    internal class ExecutionContext
    {
        public DispatcherSynchronizationContext DispatcherSynchronizationContext { get; set; }
        public Dispatcher Dispatcher { get; set; }
    }

    /*  ------   */

}