我一直认为,如果我调用异步函数,线程将开始执行此异步函数,直到看到等待状态为止。我没有闲着等待,我认为它会进入调用堆栈,以查看调用方是否在等待。如果没有,它将执行代码。
考虑以下(简化)代码:
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
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之前没有引发异常,为什么?到底是怎么回事?
显然,有些人不想要简化的代码,而是我的原始测试代码。尽管我认为这对描述问题没有帮助,但是在这里。
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;
}
}
如果您仔细观察:这也无济于事(感谢答复者和评论者,我现在知道为什么了。
答案 0 :(得分:2)
不等待任务就无法处理异常。异常仅在线程/任务内传播。因此,如果您不等待,异常将停止任务。而且,如果在您等待之前抛出了异常,那么它将在您实际等待时传播。
所以,我建议您先进行验证:
ValidateId(id); // This will throw synchronously.
Task<Customer> customer = FetchCustomerAsync(id).ConfigureAwait(false);
DoSomethingElse();
return await customer.Name;
这是实现所需并行度的最佳方法。
答案 1 :(得分:1)
您对那个线程执行异步功能直到它看到等待是正确的。实际上,您调用ArgumentOutofRangeException
的线程会抛出FetchCustmerNameAsync
。即使在同一线程中也不会出现异常的原因是,当在函数内部使用await
时,会生成AsyncStateMachine
。它将所有代码转换为状态机,但是重要的部分是它如何处理异常。看看:
此代码:
public void M() {
var t = DoWork(1);
}
public async Task DoWork(int amount)
{
if(amount == 1)
throw new ArgumentException();
await Task.Delay(1);
}
转换为(我跳过了不重要的部分):
private void MoveNext()
{
int num = <>1__state;
try
{
TaskAwaiter awaiter;
if (num != 0)
{
if (amount == 1)
{
throw new ArgumentException();
}
awaiter = Task.Delay(1).GetAwaiter();
if (!awaiter.IsCompleted)
{
// Unimportant
}
}
else
{
// Unimportant
}
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception); // Add exception to the task.
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
如果您遵循<>t__builder.SetException(exception);
(AsyncMethodBuilder.SetException
),您会发现它最终会调用task.TrySetException(exception);
,从而将异常添加到任务的exceptionHolder
中,可以使用{ {1}}属性。
答案 2 :(得分:-1)
简化的MCVE:
static async Task Main(string[] args)
{
try
{
// enable 1 of these calls
var task = DoSomethingAsync();
// var task = DoSomethingTask();
Console.WriteLine("Still Ok");
await task;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static async Task DoSomethingAsync()
{
throw new NotImplementedException();
}
private static Task DoSomethingTask()
{
throw new NotImplementedException();
return Task.CompletedTask;
}
当调用DoSomethingAsync时,您将看到“ Still Ok”消息。
调用DoSomethingTask时,您将获得预期的行为:WriteLine之前的立即异常。