【发布时间】:2018-07-31 17:39:38
【问题描述】:
我一直认为如果我调用一个异步函数,线程就会开始执行这个异步函数,直到它看到一个等待。我认为它会向上调用堆栈以查看调用者是否没有等待,而不是无所事事地等待。如果不是,则执行代码。
考虑以下(简化的)代码:
async Task<string> FetchCustomerNameAsync(int customerId)
{
// check if customerId is positive:
if (customerId <= 0) throw new ArgumentOutofRangeException(nameof(customerId);
// fetch the Customer and return the name:
Customer customer = await FetchCustomerAsync(customerId);
return customer.Name;
}
现在如果我的异步函数在没有等待的情况下调用 FetchCustomerNameAsync(+1) 会发生什么:
var myTask = FetchCustmerNameAsync(+1);
DoSomethingElse();
string customerName = await myTask;
-
FetchCustomerNameAsync,调用时参数值为+1 -
FetchCustomerNameAsync检测到customerId是肯定的,所以没有例外 -
FetchCustomerNameAsync致电FetchCustomerAsync -
FetchCustomerAsync内部的某个地方正在等待。当这种情况发生时,线程会向上调用堆栈,直到其中一个调用者没有等待。 -
FetchCustomerNameAsync正在等待,所以调用堆栈 - 我的函数还没有等待,继续
DoSomethingElse() - 我的函数遇到了等待。
我的想法是,在我的函数中等待之前,已经完成了对参数值的检查。
因此,以下情况应该会在等待之前导致异常:
// call with invalid parameter; do not await
var myTask = FetchCustmerNameAsync(-1); // <-- note the minus 1!
Debug.Assert(false, "Exception expected");
我认为虽然我没有等待,但参数值的检查是在 Debug.Assert 之前执行的。
然而,在我的程序中,在 Debug.Assert 之前没有抛出异常 为什么?到底发生了什么?
在 cmets 之后添加
显然有些人不想要简化的代码,而是我的原始测试代码。虽然我认为这无助于描述问题,但就是这样。
Microsoft about usage of local functions in C# 7.
本文描述了在等待之前不会检测到异常(就像我的问题一样)。这让我很震惊,因为我一直以为参数已经被检查过了。所以我写了一些测试代码。 (现在我知道得更好了,感谢回答者和评论者)。
所以这是我的非简化测试代码。它编译、运行并显示效果。但是,它无助于描述问题,只会分散注意力。但是对于那些在所有这些警告之后仍然感兴趣的人:
async Task DemoLocalFunctionsInAwaitAsync()
{
// using local functions after parameterchecks gives errors immediately
// No exception before await:
Task<int> task1 = OldMethodWithoutLocalFunction(null);
// See? no exception
// New method: exception even if no await
try
{
Task<int> task2 = NewMethodUsingLocalFunction(null);
// no await, yet an exception
Debug.Assert(false, "expected exception");
}
catch (ArgumentNullException)
{
// this exception is expected
}
try
{
// await the first task that did not throw an exception: expect the exception
await task1;
Debug.Assert(false, "expected exception");
}
catch (ArgumentNullException)
{
// this exception is expected
}
}
在我通常写的函数下面:
async Task<int> OldMethodWithoutLocalFunction(Customer c)
{
// this does not throw exception before awaited
if (c == null) throw new ArgumentNullException(nameof(c));
await Task.CompletedTask;
return c.CustomerId;
}
这是使用本地函数的函数。与上面提到的 Microsoft 文章中的描述差不多。
async Task<int> NewMethodUsingLocalFunction(Customer c)
{
// this method gives an exception even if not awaited yet
if (c == null) throw new ArgumentNullException(nameof(c));
return await LocalFetchCustomerIdAsync(c);
async Task<int> LocalFetchCustomerIdAsync(Customer customer)
{
await Task.CompletedTask;
return customer.CustomerId;
}
}
如果您仔细观察:这也无济于事(我现在明白为什么了,感谢回答者和评论者)。
【问题讨论】:
-
您的无效参数似乎是正数,因此不确定您为什么会期待异常。 (当然,这可能只是问题的转录错误,但这就是为什么我们更喜欢看到已执行的真实代码并且只是复制到问题中)跨度>
-
因此,与其给我们提供不是复制和粘贴的“简化”代码,请给我们minimal reproducible example。您知道此问题中的代码 演示了您要询问的问题吗?
-
您的异常已被存储并将在等待时抛出。你的调试器告诉你什么?
-
异常存储在任务中。当您等待包含异常的任务时,会在该点引发异常。因为您不等待
FetchCustmerNameAsync,所以异常只是坐在任务中。 -
或者它命名了一个不存在的异常类型,语法不正确,并且显然没有放在编译器前面。这就是我问的原因。
标签: c# async-await