为了账号安全,请及时绑定邮箱和手机立即绑定

Async-await:线程会一直运行到我的等待吗?

Async-await:线程会一直运行到我的等待吗?

C#
翻翻过去那场雪 2021-11-07 20:17:36
我一直认为如果我调用一个异步函数,线程就会开始执行这个异步函数,直到它看到一个等待。我认为它会向上调用堆栈查看调用者是否没有等待,而不是无所事事地等待。如果没有,则执行代码。考虑以下(简化的)代码: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()我的功能满足等待。我的想法是,在满足我函数中的 await 之前,已经完成了对参数值的检查。因此,以下应在 await 之前导致异常:// call with invalid parameter; do not awaitvar myTask = FetchCustmerNameAsync(-1);      // <-- note the minus 1!Debug.Assert(false, "Exception expected");我认为虽然我没有等待,但在Debug.Assert.然而,在我的程序中,在 Debug.Assert为什么之前没有抛出异常?到底发生了什么?
查看完整描述

3 回答

?
繁星淼淼

TA贡献1775条经验 获得超11个赞

异常仅在等待任务时传播

您不能在不等待任务的情况下处理异常。异常仅在线程/任务内传播。因此,如果您不等待,异常只会停止任务。如果在您等待之前抛出异常,它将在您实际等待时传播。


之前做所有的验证,然后做异步工作。

所以,我建议你之前验证:


ValidateId(id); // This will throw synchronously.

Task<Customer> customer = FetchCustomerAsync(id).ConfigureAwait(false);

DoSomethingElse();

return await customer.Name;

这是实现您想要的并行性的最佳方式。


查看完整回答
反对 回复 2021-11-07
?
慕莱坞森

TA贡献1810条经验 获得超4个赞

您是对的,该线程执行异步函数直到它看到等待。事实上,你ArgumentOutofRangeException是由你调用的线程抛出的FetchCustmerNameAsync。即使它是同一个线程也不会得到异常的原因是因为当您await在函数内部使用时,AsyncStateMachine构建了 a 。它将所有代码转换为状态机,但重要的部分是它如何处理异常。看一看:


这段代码:


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);which 将异常添加到任务的 中exceptionHolder,可以通过Task.Exception属性检索。


查看完整回答
反对 回复 2021-11-07
?
catspeake

TA贡献1111条经验 获得超0个赞

一个简化的 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 之前的立即异常。


查看完整回答
反对 回复 2021-11-07
  • 3 回答
  • 0 关注
  • 384 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信