简体   繁体   中英

xUnit test hangs/deadlocks when calling method with anonymous Async method

I have an xUnit (2.1.0) test that always hangs/deadlocks. Here the code with the names of classes/methods changed for clarity and confidentiality:

[Fact]
public void DocumentModificationTest()
{
    Exception ex = null;
    using (TestDependency testDependency = new TestDependency())
    {
        TestDependencyDocument testDependencyDoc = testDependency.Document;
        MockTestDependency fakeDependency = new MockTestDependency();
        try
        {
            DoStuffToTheDocument(testDependencyDoc, "fileName.doc", fakeDependency);
        }
        catch (Exception e)
        {
            ex = e;
        }
    }
    Assert.Null(ex);
}

If I set a breakpoint and step over until the assert I can see that ex is null and the test should pass and be done with, but it just hangs and I never see Test Successful on the runner.

Here's what DoStuffToTheDocument looks like:

public static void DoStuffToTheDocument(TestDependencyDocument document, string pFileName, MockTestDependency pContainer)
{
    pContainer.CheckInTheDocFirst(async () =>
    {
        //check some stuff
        //test returns early here

        //check other stuff(test never gets here)
        //await method (thus the async anonymous method)
    });
}

And lastly here's what CheckInTheDocFirst looks like:

public void CheckInTheDocFirst(Action pConfirmAction) 
{
    pConfirmAction(); //since this is a method in a mock class only used for testing we just call the action
}

Any ideas whats happening here? Is there something with my async-await paradigm that is causing this test to hang?

It turns out that this is an issue caused by async void test method support in xUnit: https://github.com/xunit/xunit/issues/866#issuecomment-223477634

While you really should carry async all the way up, sometimes this isn't feasible because of interoperability concerns. You can't always change the signatures of everything you're working with.

One thing to note about your assert, however, is that it will always prove true even if there's an exception thrown in the async void method (the lambda, in this case). This is because exception handling is different for async void :

Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started. Figure 2 illustrates that exceptions thrown from async void methods can't be caught naturally.

When you have an async function, you should really be async all the way down. Otherwise you run into issues with sync contexts being blocked which will result in lockups.

pContainer.CheckInTheDocFirst should be async and return a Task (since it's taking an async function that returns a Task object).

DoStuffToDocument should be an async function that returns a Task, since it calls an async function.

And finally, the test itself should also be an async method that returns a task.

If you run the async all the way up the stack, I think you'll find things just work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM