被说一知半解,await不懂你别乱用

1.前言

这篇文章我是不想写的,因为之前已经写过了(异步回调Async Await与Promise区别),此时此刻的心情是非常的不满,为什么呢? 因为前几天项目中把多层promise链式回调用了用 es6的await改写了,当我们前端XXX看到之后很疑惑,就问,为什么这么写?你知道await是什么吗?我说知道,await后面是返回的一个promise的resolve/reject的结果,这样的目的可以避免很多promise链式嵌套回调,而且await 的promise不执行完,下面代码也不会执行!代码看上去也很同步,他很不屑的看了我一眼,别一知半解的在那乱用(我们公司的领导说话口气都这样),别以为我不懂,这个await就是等待的意思!它就是个同步代码,会阻塞主线程,以前写什么XXX的时候,用这个会导致浏览器卡死不动,在后端服务器会直接DOWN掉,不知道不要乱用?,其实我很想解释,但是我觉得当时的情况,我多说无益,其实我这样写也是为了让代码更加清晰,以后维护起来也方便!无奈之举!只能改回promise的链式嵌套写法,这篇文章我会分析下我对await(一知半解)的理解!

2.await是否阻塞主线程?

先说下 await的正确使用,await必须是在这个async声明的函数内部使用,async是什么意思?异步的意思,看到这个东西也知道这个是个异步的函数,会阻塞主线程?,javascript虽然是单线程,先执行同步代码,碰到异步函数的会扔进异步队列里面,等主线程空闲了再把异步队列再把异步函数放进主线程执行,下面我来证实下我的观点

let test = async () =>{
    return await new Promise((resolve, reject)=>{
        setTimeout(() =>{
            resolve("第一个请求")
            console.log("第一个请求")
        }, 5000)
    });
}

test();

for (let i = 0; i < 10; i++) {
    setTimeout((i) =>{
        console.log("我执行了" + i + "次")
    }, 1000 * i, i);
}

按正常执行顺序,主线程执行到test函数时,如果是同步的,那么下面的for循坏更本不会执行!我们来看执行结果

在这里插入图片描述
结论:所以可以跟明确的得出结论,它不会阻塞主线程!


3.await是否同步?

首先要明白await的用法,当await后面的不是promise,它会不会阻塞?看下面代码!

function forLoop() {
   for (let i = 0; i < 10; i++) {
       setTimeout((i) =>{
           console.log("我执行了" + i + "次")
       }, 1000 * i, i);
   }
}

let test = async () => {
    await forLoop();
   console.log("我执行不执行");
}
test();

await 后面不是个promise,而是个for循环,里面有个setTimout,这个又涉及到宏任务了(先不管,暂且他看成异步),那么await 后面接到一个异步setTimout的函数,那么我下面的 console.log还会执行吗?还是会卡在这里,等setTimout函数执行完再执行呢?让结果来告诉我们!
在这里插入图片描述
结论:如果await后面接的不是promise,那么它是按正常顺序执行的,先同步后异步,需要注意的await后面属于微任务,所以,这种情况也不会阻塞

如果await 后面接的是promise,那么await后面的代码就相当在promise.then()里面执行,promise.then里面是个微任务如果不懂可以点击javascript执行机制所以我们来看下例子回忆下

setTimeout(() => {
    console.log("setTimeout宏任务")
}, 0);

let test = async () =>{
    console.log("promise 同步代码1")
    let onePromise = await new Promise((resolve, reject) => {
        resolve("success")
    });
    console.log("promise微任务执行2");
}

test();

new Promise((resolve, reject) => {
    console.log("promise 同步代码")
    resolve("success");
}).then((data) => {
    console.log("promise微任务执行1")
})
console.log("end");

简单分析下,一开始setTimeout宏任务扔进任务队列,接着执行console.log("promise 同步代码1"), console.log("promise微任务执行2")是在promise.then中执行的所以是个微任务扔进任务队列,接着执行 console.log("promise 同步代码"), 又一个 console.log("promise微任务执行1") 微任务扔进任务队列,接着执行console.log("end"),看下任务队列有没有微任务,按顺序执行 console.log("promise微任务执行2"),再执行 console.log("promise微任务执行1"),最后执行下一轮事件循环,执行 console.log("setTimeout宏任务"),后面没有微任务了,结束事件循环!
在这里插入图片描述

let test = async () => {
    let onePromise = await new Promise((resolve, reject) => {
        resolve("success")
    });
   let result = "promise微任务执行2" ;
   console.log(result); 
}

test();

//等价于

let test = async () => {
    let onePromise = await new Promise((resolve, reject) => {
        resolve("success")
    });
}
test().then((data)=>{
  let result = "promise微任务执行2" ;
  console.log(result);
});

await 后面的执行都相当于放到了Promise.resolve(),很显然是成功的回调函数!那么多个await呢?我们来模拟下

let asyncFn1 = async () => {
    await new Promise((resolve, reject) => {
        resolve("success1")
        console.log("success1");
    });
    await new Promise((resolve, reject) => {
        resolve("success2")
        console.log("success2");
    });
    await new Promise((resolve, reject) => {
        resolve("success3");
        console.log("success3");
    });
}
asyncFn1();


//等价于


let asyncFn1 = async () => {
   await new Promise((resolve, reject) => {
       resolve("success1")
       console.log("success1");
   });
}
asyncFn1().then((data) => {
   let asyncFn2 = async () => {
       await new Promise((resolve, reject) => {
           resolve("success2")
           console.log("success2");
       });
   }
   asyncFn2().then(() => {
       let asyncFn3 = async () => {
           await new Promise((resolve, reject) => {
               resolve("success3")
               console.log("success3");
           });
       }
       asyncFn3().then(() => {

       })
   });
});

在这里插入图片描述

看上面这种代码转变,那么你应该知道这个和promise链式调用有没有什么区别?为什么es6要出async,await,就是为了解决这种多个promise链式调用嵌套的,它是promise的升级版!只是在promise原有的基础上升级了,让我们调用的时候代码看起来更清晰!

let asyncFn1 = async () => {
    let onePromise = await new Promise((resolve, reject) => {
        setTimeout(function () {
            resolve("第一个请求");
            console.log("第一个请求");
        }, 5000)
    });
    console.log("我要先执行");
}

asyncFn1();

像上面这种写法是有问题,要等onePromise 执行完成之后才会执行下面的,但是你别忘记了await的初衷,你肯定不会愚蠢到把先要执行的东西放到一个promise的回调里面吧

结论:如果await后面接的是promise,那么它是严格按照代码顺序执行的,此时是同步的,因为后面的代码都是promise的成功回调


4.总结

await不会阻塞主线程,不会导致浏览器假死,卡死,anync是个异步函数,会被放进异步队列,等主线程空闲了才会放进主线程,await后面的代码如果是promise,那么是同步的,如果不是promise,那么执行规则是先同步后异步,但是你要了解await,它是为了解决promise链式调用嵌套而存在的,就算是promise链式嵌套,它的执行也是同步!所以要在合适的使用场景正确使用!(你可能写个循环都会出现死循环导致浏览器崩溃)

<think>好的,我现在需要回答用户关于C#中await关键字的作用及用法的问题。首先,我得回忆一下自己之前学过的C#异步编程相关知识,确保信息准确。 首先,await关键字的作用。记得await用于等待一个异步任务的完成,它让方法可以暂停执行,直到等待的任务完成,同时阻塞当前线程。这有助于提升程序的响应性,特别是在UI应用或需要处理大量I/O操作的时候。这一点需要明确明,可能还需要提到async和await是配合使用的,方法必须声明为async才能使用await。 然后是用法部分。用户可能需要看到具体的例子,比如如何在一个async方法中使用await来等待Task的结果。比如在UI按钮点击事件中,调用一个异步方法下载数据,用await等待,这样UI就会卡住。另外,可能需要提到ConfigureAwait(false)的作用,避免死锁,特别是在库代码中使用时,这样会回到原始上下文,提升性能。 还需要注意异常处理,await会传播任务的异常,所以通常要用try-catch块来捕获异常。此外,返回类型的问题,async方法通常返回Task、Task<T>或ValueTask<T>,避免使用void,除非是事件处理程序。 可能还需要解释状态机的概念,编译器如何将async方法转换为状态机,管理异步操作的暂停和恢复。这部分可能稍微深入,但用户可能需要太底层的细节,过作为回答的一部分,可以简要提及。 另外,用户提供的引用中有提到ConfigureAwait(false)的作用,这点需要包含在回答中,明其用途和适用场景。例如,在库代码或非UI上下文中使用,避免必要的上下文切换,提高性能。 可能还需要对比同步和异步编程的区别,强调await如何简化异步代码,使其看起来像同步代码,提高可读性和维护性。同时,提醒用户避免常见的错误,比如在非async方法中使用await,或者错误地处理Task对象导致阻塞。 最后,根据用户提供的引用内容,引用[1]提到async和await简化异步编程,提高性能和用户体验,引用[2]关于ConfigureAwait的作用,引用[3]展示了await Task.Run的用法。这些都需要自然地融入到回答中,并在适当的位置添加引用标识。 总结一下,回答的结构应该是先解释作用,再明用法,给出示例代码,然后讨论注意事项和最佳实践,最后生成相关问题。需要确保使用正确的LaTeX格式,行内公式用$...$,独立公式用$$...$$,过这个问题可能涉及数学公式,但要注意代码块的使用。同时,用中文回答,保持结构清晰,帮助用户逐步理解。</think>### C# 中 `await` 关键字的作用及用法 #### 作用 `await` 是 C# 中用于简化异步编程的关键字,**核心作用**是让当前异步方法暂停执行,等待一个异步操作完成,同时释放当前线程以执行其他任务,从而提升程序的响应性和资源利用率[^1]。它必须与 `async` 修饰的方法配合使用,且通常用于 `Task` 或 `Task<TResult>` 对象前。 #### 用法 1. **基本语法** 在 `async` 方法中,`await` 用于等待异步操作完成: ```csharp public async Task<string> FetchDataAsync() { HttpClient client = new HttpClient(); string result = await client.GetStringAsync("https://example.com"); return result; } ``` 此示例中,`await` 会暂停 `FetchDataAsync` 方法的执行,直到 `GetStringAsync` 完成,期间线程可处理其他任务[^3]。 2. **与 `Task.Run` 结合** 若需在后台线程执行耗时操作: ```csharp public async void Button_Click(object sender, EventArgs e) { string result = await Task.Run(() => ComputeHeavyOperation()); MessageBox.Show(result); } ``` `Task.Run` 将 `ComputeHeavyOperation` 放入线程池执行,`await` 确保 UI 线程被阻塞。 3. **异常处理** `await` 会自动传播异步操作的异常,需用 `try-catch` 捕获: ```csharp try { await SomeAsyncMethod(); } catch (Exception ex) { // 处理异常 } ``` 4. **配置上下文:`ConfigureAwait(false)`** 在非 UI 场景(如库代码)中,使用 `ConfigureAwait(false)` 避免回到原始上下文,可减少死锁风险并提升性能[^2]: ```csharp var data = await SomeAsyncMethod().ConfigureAwait(false); ``` #### 注意事项 - **返回类型**:`async` 方法通常返回 `Task` 或 `Task<T>`,`void` 仅用于事件处理程序。 - **避免阻塞**:要对异步方法调用使用 `.Result` 或 `.Wait()`,否则可能导致死锁。 - **性能优化**:合理使用 `ValueTask<T>` 替代 `Task<T>` 以减少堆分配。 #### 示例场景 ```csharp public async Task LoadDataAsync() { // 等待数据加载,UI 保持响应 var data = await LoadFromDatabaseAsync(); // 等待结果处理,阻塞主线程 await ProcessDataAsync(data).ConfigureAwait(false); } ``` ---
评论 48
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值