async与await异步编程
Posted 尘世冒险家
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了async与await异步编程相关的知识,希望对你有一定的参考价值。
ECMA2017中新加入了两个关键字async与await
简单来说它们是基于promise之上的的语法糖,可以让异步操作更加地简单明了
首先我们需要用async关键字,将函数标记为异步函数
async function f()
f()
异步函数就是指:返回值为promise对象的函数
比如之前用到的fetch()就是一个异步函数,返回的是promise
在异步函数中,我们可以调用其他的异步函数,不过我们不再需要使用then,而是使用一个await。
await会等待Promise完成之后直接返回最终结果
所以这里的response已经是一个服务器返回的响应数据了
async function f()
const response = await fetch("http://....")
f()
虽然await看上去会暂停函数的执行,但在等待的过程中,js同样可以处理其他的任务
这是因为await底层是基于promise与事件循环(event loop)机制实现的
await使用时的陷阱:
1、第一个陷阱
比如:我们分别去await这两个异步操作
async function f()
const a = fetch("http://..../post/1")
const b = fetch("http://..../post/2")
f()
虽然不存在逻辑错误
但这样会打破这两个fetch()操作的并行
因为我们会等到第一个任务执行完成之后才开始执行第二个任务
这里更高效的方法是将所有的Promise用Promise.all组合起来,然后再去await:
修改之后的执行效率会直接提升一倍
async function f()
const promiseA = fetch("http://..../post/1")
const promiseB = fetch("http://..../post/2")
const [a, b] = await Promise.all([promiseA,promiseB])
f()
2、第二个陷阱
如果我们需要在循环中执行异步操作,是不能够直接调用forEach或者map这一类方法的,尽管我们在回调函数中写了await也不行。
因为这里的forEach会立即返回,它并不会等到所有的异步操作都执行完毕
async function f()
[1,2,3].forEach(async (i) =>
await someAsyncOperation();
)
console.log("done")
f()
如果我们希望等待循环中的异步操作都一一完成之后才继续执行
我们应当使用传统的for循环
async function f()
for( let i of [1,2,3])
await someAsyncOperation();
console.log("done")
f()
如果我们希望所有的程序并发执行,一种更炫酷的写法就是使用for await
这里的for循环依然会等到所有的异步操作都完成之后才会继续向后执行
3、第三个陷阱
我们不能在全局或者普通函数中直接使用await关键字
await只能用在异步函数(async function)中
如果我们想要在最外层中使用await,那么需要先定义一个异步函数:
使用await async可以让我们写出更清晰,更容易理解的异步代码
多线程之异步编程: 经典和最新的异步编程模型,async与await
经典的异步编程模型(IAsyncResult)
- 最新的异步编程模型(async 和 await)
- 将 IAsyncInfo 转换成 Task
- 将 Task 转换成 IAsyncInfo
示例
1、使用经典的异步编程模型(IAsyncResult)实现一个支持异步操作的类
Thread/Async/ClassicAsync.cs
/* * 使用经典的异步编程模型(IAsyncResult)实现一个支持异步操作的类 */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace XamlDemo.Thread.Async { public class ClassicAsync { private delegate string HelloDelegate(string name); private HelloDelegate _helloDelegate; public ClassicAsync() { _helloDelegate = new HelloDelegate(Hello); } private string Hello(string name) { new ManualResetEvent(false).WaitOne(3000); return "hello: " + name; } // begin 方法 public IAsyncResult BeginRun(string name, AsyncCallback callback, Object state) { // 新开线程,去执行 Hello() 方法,callback 是回调,state 是上下文 return _helloDelegate.BeginInvoke(name, callback, state); } // end 方法 public string EndRun(IAsyncResult ar) { if (ar == null) throw new NullReferenceException("IAsyncResult 不能为 null"); return _helloDelegate.EndInvoke(ar); } } }
Thread/Async/ClassicAsyncDemo.xaml
<Page x:Class="XamlDemo.Thread.Async.ClassicAsyncDemo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.Thread.Async" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <StackPanel Margin="120 0 0 0"> <TextBlock Name="lblMsg" FontSize="14.667" /> <Button Name="btnIAsyncResult" Content="IAsyncResult 的 Demo" Click="btnIAsyncResult_Click_1" Margin="0 10 0 0" /> </StackPanel> </Grid> </Page>
Thread/Async/ClassicAsyncDemo.xaml.cs
/* * 演示如何通过经典的异步编程模型(IAsyncResult)来进行异步操作 * * IAsyncResult - 异步操作结果 * AsyncState - 上下文 * IsCompleted - 异步操作是否已完成 * AsyncWaitHandle - 获取用于等待异步操作完成的 System.Threading.WaitHandle 对象(通过 WaitHandle.WaitOne() 在当前线程等待) */ using System; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace XamlDemo.Thread.Async { public sealed partial class ClassicAsyncDemo : Page { System.Threading.SynchronizationContext _syncContext; public ClassicAsyncDemo() { this.InitializeComponent(); // 获取当前 UI 线程 _syncContext = System.Threading.SynchronizationContext.Current; } private void btnIAsyncResult_Click_1(object sender, RoutedEventArgs e) { ClassicAsync classicAsync = new ClassicAsync(); IAsyncResult ar = classicAsync.BeginRun("webabcd", new AsyncCallback(Callback), classicAsync); lblMsg.Text = "开始执行,3 秒后完成"; } private void Callback(IAsyncResult ar) { ClassicAsync classicAsync = (ClassicAsync)ar.AsyncState; string result = classicAsync.EndRun(ar); _syncContext.Post( (ctx) => { lblMsg.Text = result; }, null); } } }
2、演示如何通过最新的异步编程模型(async 和 await)来进行异步操作
Thread/Async/NewAsyncDemo.xaml
<Page x:Class="XamlDemo.Thread.Async.NewAsyncDemo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.Thread.Async" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <StackPanel Margin="120 0 0 0"> <TextBlock Name="lblMsg" FontSize="14.667" /> <Button Name="btnTaskWithoutReturn" Content="执行一个不带返回值的 Task" Click="btnTaskWithoutReturn_Click_1" Margin="0 10 0 0" /> <Button Name="btnTaskWithReturn" Content="执行一个带返回值的 Task" Click="btnTaskWithReturn_Click_1" Margin="0 10 0 0" /> <Button Name="btnMultiTask" Content="并行执行多个 Task" Click="btnMultiTask_Click_1" Margin="0 10 0 0" /> <Button Name="btnTaskWithoutAwait" Content="执行一个不 await 的 Task" Click="btnTaskWithoutAwait_Click_1" Margin="0 10 0 0" /> </StackPanel> </Grid> </Page>
Thread/Async/NewAsyncDemo.xaml.cs
/* * 演示如何通过最新的异步编程模型(async 和 await)来进行异步操作 * * 注: * 1、要想 await,其所在方法必须标记为 async * 2、方法被标记为 async 是为了让编译器重新编写该方法,使 await 中的内容重新编写为具有 GetAwaiter() 等实际异步逻辑的代码 */ using System; using System.Threading; using System.Threading.Tasks; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace XamlDemo.Thread.Async { public sealed partial class NewAsyncDemo : Page { private static int _count = 0; public NewAsyncDemo() { this.InitializeComponent(); } // 不带返回值的 Task private async Task TaskWithoutReturn() { // 在另一个线程 sleep 1000 毫秒,然后回到 UI 线程 await Task.Delay(1000); // await Task.Delay(Timeout.Infinite); 长眠于此 // await Task.Delay(Timeout.InfiniteTimeSpan); 长眠于此 // 直接在当前线程 sleep 可以使用如下方法,因为 WinRT 中没有 Thread.Sleep() 了 // new ManualResetEvent(false).WaitOne(1000); Interlocked.Increment(ref _count); } // 带返回值的 Task private async Task<int> TaskWithReturn() { await Task.Delay(1000); Interlocked.Increment(ref _count); return _count; } // 演示不带返回值的异步操作 private async void btnTaskWithoutReturn_Click_1(object sender, RoutedEventArgs e) { // ConfigureAwait(false) - 异步操作后不返回 UI 线程,可节省一点点资源。默认值:ConfigureAwait(true) await TaskWithoutReturn().ConfigureAwait(false); lblMsg.Text = "count: " + _count.ToString(); } // 演示带返回值的异步操作 private async void btnTaskWithReturn_Click_1(object sender, RoutedEventArgs e) { int result = await TaskWithReturn(); lblMsg.Text = "count: " + result.ToString(); } // 演示多任务并行执行的异步操作 private async void btnMultiTask_Click_1(object sender, RoutedEventArgs e) { Task task = Task.WhenAll(TaskWithoutReturn(), TaskWithoutReturn(), TaskWithoutReturn()); DateTime dt = DateTime.Now; await task; lblMsg.Text = "count: " + _count.ToString() + ", 执行时间: " + (DateTime.Now - dt).TotalSeconds.ToString() + "秒"; } // 演示如何执行一个不 await 的 Task private void btnTaskWithoutAwait_Click_1(object sender, RoutedEventArgs e) { // 让 task 在新线程执行去吧,本线程不管它是什么执行情况 Task task = TaskWithoutReturn(); lblMsg.Text = "count: " + _count.ToString(); } } }
3、演示如何将 IAsyncInfo(IAsyncAction, IAsyncOperation, IAsyncActionWithProgress, IAsyncOperationWithProgress) 转成 Task
Thread/Async/IAsyncInfo2Task.xaml
<Page x:Class="XamlDemo.Thread.Async.IAsyncInfo2Task" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.Thread.Async" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <StackPanel Margin="120 0 0 0"> <TextBlock Name="lblMsg" FontSize="14.667" /> </StackPanel> </Grid> </Page>
Thread/Async/IAsyncInfo2Task.xaml.cs
/* * 演示如何将 IAsyncInfo(IAsyncAction, IAsyncOperation, IAsyncActionWithProgress, IAsyncOperationWithProgress) 转成 Task */ using System; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading; using System.Threading.Tasks; using Windows.Foundation; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; namespace XamlDemo.Thread.Async { public sealed partial class IAsyncInfo2Task : Page { public IAsyncInfo2Task() { this.InitializeComponent(); } protected async override void OnNavigatedTo(NavigationEventArgs e) { // 用于取消 Task CancellationTokenSource cts = new CancellationTokenSource(); // 创建一个 IAsyncInfo IAsyncOperation<int> action = AsyncInfo.Run<int>( (token) => Task.Run<int>( () => { token.WaitHandle.WaitOne(3000); token.ThrowIfCancellationRequested(); return 10 * 10; }, token)); lblMsg.Text = "开始执行,3 秒后完成"; // 将 IAsyncOperation 转换成 Task // AsTask() 是扩展方法,其逻辑在 System.WindowsRuntimeSystemExtensions 类中 Task<int> task = action.AsTask<int>(cts.Token); int result = await task; lblMsg.Text = "结果:" + result.ToString(); } } }
4、演示如何将 Task 转成 IAsyncInfo(IAsyncAction, IAsyncOperation)
Thread/Async/Task2IAsyncInfo.xaml
<Page x:Class="XamlDemo.Thread.Async.Task2IAsyncInfo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.Thread.Async" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <StackPanel Margin="120 0 0 0"> <TextBlock Name="lblMsg" FontSize="14.667" /> </StackPanel> </Grid> </Page>
Thread/Async/Task2IAsyncInfo.xaml.cs
/* * 演示如何将 Task 转成 IAsyncInfo(IAsyncAction, IAsyncOperation) */ using System; using System.Threading; using System.Threading.Tasks; using Windows.Foundation; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; namespace XamlDemo.Thread.Async { public sealed partial class Task2IAsyncInfo : Page { public Task2IAsyncInfo() { this.InitializeComponent(); } protected async override void OnNavigatedTo(NavigationEventArgs e) { // 用于取消 IAsyncInfo(注意:本例中的 IAsyncInfo 是从 Task 转换过来的,所以 IAsyncInfo.Cancel() 方法无效) CancellationTokenSource cts = new CancellationTokenSource(); // 创建一个 Task Task<int> task = Task.Run<int>( () => { cts.Token.WaitHandle.WaitOne(3000); cts.Token.ThrowIfCancellationRequested(); return 10 * 10; }, cts.Token); lblMsg.Text = "开始执行,3 秒后完成"; // 将 Task 转换成 IAsyncOperation // AsAsyncAction(), AsAsyncOperation() 是扩展方法,其逻辑在 System.WindowsRuntimeSystemExtensions 类中 IAsyncOperation<int> operation = task.AsAsyncOperation<int>(); int result = await operation; lblMsg.Text = "结果:" + result.ToString(); } } }
以上是关于async与await异步编程的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript异步编程:Generator与Async
多线程之异步编程: 经典和最新的异步编程模型,async与await