C# 传递异步操作时出现意外行为

C# 传递异步操作时出现意外行为,c#,mstest,async-await,visual-studio-2013,C#,Mstest,Async Await,Visual Studio 2013,我非常熟悉async/await模式,但是我碰到了一些让我觉得奇怪的行为。我相信这是一个完全合理的原因,我很想了解这种行为 这里的背景是我正在开发一个Windows应用商店应用程序,因为我是一个谨慎、认真的开发人员,所以我正在对所有东西进行单元测试。我很快发现WSAs不存在ExpectedExceptionAttribute。奇怪吧?好吧,没问题!我可以用扩展方法或多或少地复制这个行为!所以我写了这个: public static class TestHelpers { // There

我非常熟悉async/await模式,但是我碰到了一些让我觉得奇怪的行为。我相信这是一个完全合理的原因,我很想了解这种行为

这里的背景是我正在开发一个Windows应用商店应用程序,因为我是一个谨慎、认真的开发人员,所以我正在对所有东西进行单元测试。我很快发现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");
    }
}
我调整了我的测试:

[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>();
}
[TestMethod]
公共异步任务网络\u接口\u不可用\u引发\u异常()
{
var webManager=new FakeWebManager
{
IsNetworkAvailable=false
};
var am=新的AuthenticationManager(webManager);
Func authenticate=async()=>等待am.authenticate(“foo”、“bar”);
等待身份验证。AssertThrowsExpectedException();
}

我对我的解决方案很满意,我只是想知道,当我尝试调用异步
操作时,为什么一切都会变成梨形。我猜测是因为,就运行时而言,它不是一个
操作
,我只是把lambda塞进了它。我知道lambda将很高兴地被分配到
Action
Func

在您的第二个代码片段场景中,它可能会使测试仪崩溃,这并不奇怪:

Action authenticate = async () => await am.Authenticate("foo", "bar");
authenticate.AssertThrowsExpectedException<LoginFailedException>();
a()
会立即返回,
AssertThrowsExpectedException
方法也会立即返回。同时,在
am内部启动了一些活动。Authenticate
可能会继续在后台执行,可能是在池线程上执行。那里到底发生了什么取决于
am.Authenticate
的实现,但当异步操作完成并抛出
LoginFailedException
时,它可能会使测试仪崩溃。我不确定单元测试执行环境的同步上下文是什么,但是如果它使用默认的
SynchronizationContext
,在这种情况下,异常可能确实会在不同的线程上被抛出而未被注意到


VS2012自动支持异步单元测试,只要测试方法签名是
async Task
。因此,我认为您已经回答了自己的问题,在测试中使用了
wait
Func

您还应该注意,在异步方法中,您正在测试,您必须在调用该方法的线程上抛出错误,否则调用方将无法捕获该错误,并且即使使用您的解决方案,仍然会导致运行时异常。@geezer498,我相信OP的解决方案是正确的。
尝试{wait a();}catch…
。仅供参考,MSTest使用默认(线程池)任务调度器,因此在线程池线程上引发异常,可能会使测试运行程序崩溃。我之所以说“可能”,是因为从技术上讲,试跑者和例外者之间存在一种竞赛条件;e、 例如,如果异常被延迟,那么测试运行程序实际上可能会在崩溃之前完成。是的,我认为这是“凌晨1点不编码”的事情之一——现在看起来更明显了。我不是说解决方案是错误的。我是说,如果Func在另一个线程上抛出异常,解决方案将不起作用,因为这意味着异常将被抛出try-catch的范围之外。基本上,我是说,要小心编写Func中的所有代码,这样它就会在测试中可捕获的线程上抛出错误。在Windows应用商店测试中,使用
Assert.ThrowsException
,它()支持
async
lambdas。请注意,
Action
是一个没有返回值的同步方法,而
Func
是一个没有返回值的异步方法。噢,我没有注意到
Assert.ThrowsException
。我将切换我的测试以使用它。
[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 authenticate = async () => await am.Authenticate("foo", "bar");
authenticate.AssertThrowsExpectedException<LoginFailedException>();
try
{
    a();
}