c# – 传递异步操作时出现意外的行为

我非常熟悉异步/等待模式,但我碰到了一些令我奇怪的行为.我相信有一个完全有效的原因,为什么会发生,我很乐意了解行为.

这里的背景是我正在开发一个Windows Store应用程序,由于我是一个谨慎,认真的开发人员,所以我单位测试一切.我很快发现,WSAs不存在ExpectedExceptionAttribute.奇怪,对吧?好吧,没问题!我可以使用扩展方法来复制行为.所以我写道:

public static class TestHelpers
{
    // There's no ExpectedExceptionAttribute for Windows Store apps! Why must Microsoft make my life so hard?!
    public static void AssertThrowsExpectedException<T>(this Action a) where T : Exception
    {
        try
        {
            a();
        }
        catch (T)
        {
            return;
        }

        Assert.Fail("The expected exception was not thrown");
    }
}

而且,它的作品精美.

所以我继续愉快地写我的单元测试,直到我打了一个异步方法,我想确认在某些情况下抛出异常. “没问题,”我想到自己,“我可以通过一个异步的lambda!”

所以我写了这个测试方法

[TestMethod]
public async Task Network_Interface_Being_Unavailable_Throws_Exception()
{
    var webManager = new FakeWebManager
    {
        IsNetworkAvailable = false
    };

    var am = new AuthenticationManager(webManager);
    Action authenticate = async () => await am.Authenticate("foo","bar");
    authenticate.AssertThrowsExpectedException<LoginFailedException>();
}

令人惊讶的是,这会引发运行时错误.它实际上崩溃了测试者!

我重载了我的AssertThrowsExpectedException方法

public static async Task AssertThrowsExpectedException<TException>(this Func<Task> a) where TException : Exception
{
    try
    {
        await a();
    }
    catch (TException)
    {
        return;
    }

    Assert.Fail("The expected exception was not thrown");
}

我调整了我的测试:

[TestMethod]
public async Task Network_Interface_Being_Unavailable_Throws_Exception()
{
    var webManager = new FakeWebManager
    {
        IsNetworkAvailable = false
    };

    var am = new AuthenticationManager(webManager);
    Func<Task> authenticate = async () => await am.Authenticate("foo","bar");
    await authenticate.AssertThrowsExpectedException<LoginFailedException>();
}

我的解决方案很好,我只是想知道为什么一切都变成梨形,当我尝试调用异步Action.我猜是因为,就运行时而言,这不是一个Action,我只是把lambda加入到它里面.我知道lambda将很乐意分配给Action或Func< Task>.

解决方法

在第二个代码片段的情况下,测试人员可能会崩溃并不奇怪:
Action authenticate = async () => await am.Authenticate("foo","bar");
authenticate.AssertThrowsExpectedException<LoginFailedException>();

当你打电话时,这实际上是一个火灾忘记的an async void method

try
{
    a();
}

a()立即返回,AssertThrowsExpectedException方法也是如此.同时,一些活动在am.Athenticate内可以在后台继续执行,可能在一个池线程上.究竟发生了什么取决于am.Authenticate的实现,但是当这样的异步操作完成并且它抛出LoginFailedException时,它可能会使您的测试程序崩溃.我不知道单元测试执行环境的同步上下文是什么,但是如果使用认的SynchronizationContext,那么在这种情况下,异常可能会被抛出不同的线程.

VS2012自动支持异步单元测试,只要测试方法签名是异步任务即可.所以,我想你已经回答了你自己的问题,使用await和Func< T>为你的测试.

相关文章

在要实现单例模式的类当中添加如下代码:实例化的时候:frmC...
1、如果制作圆角窗体,窗体先继承DOTNETBAR的:public parti...
根据网上资料,自己很粗略的实现了一个winform搜索提示,但是...
近期在做DSOFramer这个控件,打算自己弄一个自定义控件来封装...
今天玩了一把WMI,查询了一下电脑的硬件信息,感觉很多代码都...
最近在研究WinWordControl这个控件,因为上级要求在系统里,...