使用 Go 实现 Async/Await 模式

别说谁变了你拦得住时间么 提交于 2021-01-05 10:33:19

<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px; color: black; padding: 0 10px; line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; word-break: break-word; word-wrap: break-word; text-align: left; font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;"><figure data-tool="mdnice编辑器" style="margin: 0; margin-top: 10px; margin-bottom: 10px; display: flex; flex-direction: column; justify-content: center; align-items: center;"><img src="https://static01.imgkr.com/temp/fdd0971448594f78844d2fcf2eb9e59b.png" alt style="display: block; margin: 0 auto; max-width: 100%;"></figure> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;"><strong style="font-weight: bold; color: black;">概述</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">Golang 是一种并发编程语言。它具有强大的特性,如 Goroutines 和 Channels,可以很好地处理异步任务。另外,goroutines 不是 OS 线程,这就是为什么您可以在不增加开销的情况下根据需要启动任意数量的 goroutine 的原因,它的堆栈大小初始化时仅 2KB。那么为什么要 async/await 呢?Async/Await 是一种很好的语言特点,它为异步编程提供了更简单的接口。</p> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">项目链接:https://github.com/icyxp/AsyncGoDemo</p> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;"><strong style="font-weight: bold; color: black;">它是如何工作的?</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">从 F# 开始,然后是 C#,到现在 Python 和 Javascript 中,async/await 是一种非常流行的语言特点。它简化了异步方法的执行结构并且读起来像同步代码。对于开发人员来说更容易理解。让我们看看 c# 中的一个简单示例 async/await 是如何工作的。</p> <pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block; background: url(https://files.mdnice.com/point.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #282c34; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #abb2bf; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #282c34; border-radius: 5px;">static&nbsp;async&nbsp;Task&nbsp;Main(string[]&nbsp;args)<br><br>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;Console.WriteLine(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Let's&nbsp;start&nbsp;..."</span>);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">done</span>&nbsp;=&nbsp;DoneAsync();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;Console.WriteLine(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Done&nbsp;is&nbsp;running&nbsp;..."</span>);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;Console.WriteLine(await&nbsp;<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">done</span>);<br><br>}<br><br>static&nbsp;async&nbsp;Task&lt;int&gt;&nbsp;<span class="hljs-function" style="line-height: 26px;"><span class="hljs-title" style="color: #61aeee; line-height: 26px;">DoneAsync</span></span>()<br><br>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;Console.WriteLine(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Warming&nbsp;up&nbsp;..."</span>);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;await&nbsp;Task.Delay(3000);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;Console.WriteLine(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Done&nbsp;..."</span>);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-built_in" style="color: #e6c07b; line-height: 26px;">return</span>&nbsp;1;<br><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">当程序运行时,我们的 Main 函数将被执行。我们有异步函数 DoneAsync。我们使用 Delay 方法停止执行代码 3 秒钟。Delay 本身是一个异步函数,所以我们用 await 来调用它。</p> <blockquote class="multiquote-1" data-tool="mdnice编辑器" style="border: none; display: block; font-size: 0.9em; overflow: auto; overflow-scrolling: touch; border-left: 3px solid rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.05); color: #6a737d; padding-top: 10px; padding-bottom: 10px; padding-left: 20px; padding-right: 10px; margin-bottom: 20px; margin-top: 20px;"> <p style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0px; color: black; line-height: 26px;">await 只阻塞异步函数内的代码执行</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在 Main 函数中,我们不使用 await 来调用 DoneAsync。但 DoneAsync 开始执行后,只有当我们 await 它的时候,我们才会得到结果。执行流程如下所示:</p> <pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block; background: url(https://files.mdnice.com/point.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #282c34; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #abb2bf; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #282c34; border-radius: 5px;">Let<span class="hljs-string" style="color: #98c379; line-height: 26px;">'s&nbsp;start&nbsp;...<br>Warming&nbsp;up&nbsp;...<br>Done&nbsp;is&nbsp;running&nbsp;...<br>Done&nbsp;...<br>1<br></span></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">对于异步执行,这看起来非常简单。让我们看看如何使用 Golang 的 Goroutines 和 Channels 来做到这一点。</p> <pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block; background: url(https://files.mdnice.com/point.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #282c34; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #abb2bf; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #282c34; border-radius: 5px;">func&nbsp;DoneAsync()&nbsp;chan&nbsp;int&nbsp;{<br><br>&nbsp;r&nbsp;:=&nbsp;make(chan&nbsp;int)<br><br>&nbsp;fmt.Println(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Warming&nbsp;up&nbsp;..."</span>)<br><br>&nbsp;go&nbsp;<span class="hljs-function" style="line-height: 26px;"><span class="hljs-title" style="color: #61aeee; line-height: 26px;">func</span></span>()&nbsp;{<br><br>&nbsp;&nbsp;time.Sleep(3&nbsp;*&nbsp;time.Second)<br><br>&nbsp;&nbsp;r&nbsp;&lt;-&nbsp;1<br><br>&nbsp;&nbsp;fmt.Println(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Done&nbsp;..."</span>)<br><br>&nbsp;}()<br><br>&nbsp;<span class="hljs-built_in" style="color: #e6c07b; line-height: 26px;">return</span>&nbsp;r<br><br>}<br><br>func&nbsp;<span class="hljs-function" style="line-height: 26px;"><span class="hljs-title" style="color: #61aeee; line-height: 26px;">main</span></span>&nbsp;()&nbsp;{<br><br>&nbsp;fmt.Println(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Let's&nbsp;start&nbsp;..."</span>)<br><br>&nbsp;val&nbsp;:=&nbsp;DoneAsync()<br><br>&nbsp;fmt.Println(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Done&nbsp;is&nbsp;running&nbsp;..."</span>)<br><br>&nbsp;fmt.Println(&lt;-&nbsp;val)<br><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在这里,DoneAsync 异步运行并返回一个 channel。执行完异步任务后,它会将值写入 channel。在 main 函数中,我们调用 DoneAsync 并继续执行后续操作,然后从返回的 channel 读取值。它是一个阻塞调用,等待直到将值写入 channel,并在获得值后将其输出到终端。</p> <pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block; background: url(https://files.mdnice.com/point.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #282c34; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #abb2bf; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #282c34; border-radius: 5px;">Let<span class="hljs-string" style="color: #98c379; line-height: 26px;">'s&nbsp;start&nbsp;...<br>Warming&nbsp;up&nbsp;...<br>Done&nbsp;is&nbsp;running&nbsp;...<br>Done&nbsp;...<br>1<br></span></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">我们看到,我们实现了与 C# 程序相同的结果,但它看起来不像 async/await 那样优雅。尽管这确实不错,但是我们可以使用这种方法轻松地完成很多细粒度的事情,我们还可以用一个简单的结构和接口在 Golang 中实现 async/await 关键字。让我们试试。</p> <blockquote class="multiquote-1" data-tool="mdnice编辑器" style="border: none; display: block; font-size: 0.9em; overflow: auto; overflow-scrolling: touch; border-left: 3px solid rgba(0, 0, 0, 0.4); background: rgba(0, 0, 0, 0.05); color: #6a737d; padding-top: 10px; padding-bottom: 10px; padding-left: 20px; padding-right: 10px; margin-bottom: 20px; margin-top: 20px;"> <p style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0px; color: black; line-height: 26px;">实现 Async/Await</p> </blockquote> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">完整代码可在项目链接中找到(在文章开始的地方)。要在 Golang 中实现 async/await,我们将从一个名为 async 的包目录开始。项目结构看起来是这样的。</p> <pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block; background: url(https://files.mdnice.com/point.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #282c34; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #abb2bf; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #282c34; border-radius: 5px;">.<br>├──&nbsp;async<br>│&nbsp;&nbsp;&nbsp;└──&nbsp;async.go<br>├──&nbsp;main.go<br>└──&nbsp;README.md&nbsp;<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">在 async 文件中,我们编写了可以处理异步任务最简单的 Future 接口。</p> <pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block; background: url(https://files.mdnice.com/point.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #282c34; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #abb2bf; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #282c34; border-radius: 5px;">package&nbsp;async<br><br>import&nbsp;<span class="hljs-string" style="color: #98c379; line-height: 26px;">"context"</span><br><br>//&nbsp;Future&nbsp;interface&nbsp;has&nbsp;the&nbsp;method&nbsp;signature&nbsp;<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">for</span>&nbsp;await<br><br><span class="hljs-built_in" style="color: #e6c07b; line-height: 26px;">type</span>&nbsp;Future&nbsp;interface&nbsp;{<br><br>&nbsp;Await()&nbsp;interface{}<br><br>}<br><br><span class="hljs-built_in" style="color: #e6c07b; line-height: 26px;">type</span>&nbsp;future&nbsp;struct&nbsp;{<br><br>&nbsp;await&nbsp;func(ctx&nbsp;context.Context)&nbsp;interface{}<br><br>}<br><br>func&nbsp;(f&nbsp;future)&nbsp;Await()&nbsp;interface{}&nbsp;{<br><br>&nbsp;<span class="hljs-built_in" style="color: #e6c07b; line-height: 26px;">return</span>&nbsp;f.await(context.Background())<br><br>}<br><br>//&nbsp;Exec&nbsp;executes&nbsp;the&nbsp;async&nbsp;<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">function</span><br><br>func&nbsp;Exec(f&nbsp;func()&nbsp;interface{})&nbsp;Future&nbsp;{<br><br>&nbsp;var&nbsp;result&nbsp;interface{}<br><br>&nbsp;c&nbsp;:=&nbsp;make(chan&nbsp;struct{})<br><br>&nbsp;go&nbsp;<span class="hljs-function" style="line-height: 26px;"><span class="hljs-title" style="color: #61aeee; line-height: 26px;">func</span></span>()&nbsp;{<br><br>&nbsp;&nbsp;defer&nbsp;close(c)<br><br>&nbsp;&nbsp;result&nbsp;=&nbsp;f()<br><br>&nbsp;}()<br><br>&nbsp;<span class="hljs-built_in" style="color: #e6c07b; line-height: 26px;">return</span>&nbsp;future{<br><br>&nbsp;&nbsp;await:&nbsp;func(ctx&nbsp;context.Context)&nbsp;interface{}&nbsp;{<br><br>&nbsp;&nbsp;&nbsp;select&nbsp;{<br><br>&nbsp;&nbsp;&nbsp;<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">case</span>&nbsp;&lt;-ctx.Done():<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-built_in" style="color: #e6c07b; line-height: 26px;">return</span>&nbsp;ctx.Err()<br><br>&nbsp;&nbsp;&nbsp;<span class="hljs-keyword" style="color: #c678dd; line-height: 26px;">case</span>&nbsp;&lt;-c:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span class="hljs-built_in" style="color: #e6c07b; line-height: 26px;">return</span>&nbsp;result<br><br>&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;},<br><br>&nbsp;}<br><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">这里发生的事情并不多,我们添加了一个具有 Await 方法标识的 Future 接口。接下来,我们添加一个 future 结构,它包含一个值,即 await 函数的函数标识。现在 futute struct 通过调用自己的 await 函数来实现 Future 接口的 Await 方法。</p> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">接下来在 Exec 函数中,我们在 goroutine 中异步执行传递的函数。然后返回 await 函数。它等待 channel 关闭或 context 读取。基于最先发生的情况,它要么返回错误,要么返回作为接口的结果。</p> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">现在,有了这个新的 async 包,让我们看看如何更改当前的 go 代码:</p> <pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;"><span style="display: block; background: url(https://files.mdnice.com/point.png); height: 30px; width: 100%; background-size: 40px; background-repeat: no-repeat; background-color: #282c34; margin-bottom: -7px; border-radius: 5px; background-position: 10px 10px;"></span><code class="hljs" style="overflow-x: auto; padding: 16px; color: #abb2bf; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px; -webkit-overflow-scrolling: touch; padding-top: 15px; background: #282c34; border-radius: 5px;">func&nbsp;DoneAsync()&nbsp;int&nbsp;{<br><br>&nbsp;fmt.Println(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Warming&nbsp;up&nbsp;..."</span>)<br><br>&nbsp;time.Sleep(3&nbsp;*&nbsp;time.Second)<br><br>&nbsp;fmt.Println(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Done&nbsp;..."</span>)<br><br>&nbsp;<span class="hljs-built_in" style="color: #e6c07b; line-height: 26px;">return</span>&nbsp;1<br><br>}<br><br>func&nbsp;<span class="hljs-function" style="line-height: 26px;"><span class="hljs-title" style="color: #61aeee; line-height: 26px;">main</span></span>()&nbsp;{<br><br>&nbsp;fmt.Println(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Let's&nbsp;start&nbsp;..."</span>)<br><br>&nbsp;future&nbsp;:=&nbsp;async.Exec(func()&nbsp;interface{}&nbsp;{<br><br>&nbsp;&nbsp;<span class="hljs-built_in" style="color: #e6c07b; line-height: 26px;">return</span>&nbsp;DoneAsync()<br><br>&nbsp;})<br><br>&nbsp;fmt.Println(<span class="hljs-string" style="color: #98c379; line-height: 26px;">"Done&nbsp;is&nbsp;running&nbsp;..."</span>)<br><br>&nbsp;val&nbsp;:=&nbsp;future.Await()<br><br>&nbsp;fmt.Println(val)<br><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">乍一看,它看起来干净得多,这里我们没有显式地使用 goroutine 或 channels。我们的 DoneAsync 函数已更改为完全同步的性质。在 main 函数中,我们使用 async 包的Exec 方法来处理 DoneAsync。在开始执行 DoneAsync。控制流返回到可以执行其他代码的 main 函数中。最后,我们对 Await 进行阻塞调用并回读数据。</p> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">现在,代码看起来更加简单易读。我们可以修改我们的 async 包从而能在 Golang 中合并许多其他类型的异步任务,但在本教程中,我们现在只坚持简单的实现。</p> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;"><strong style="font-weight: bold; color: black;">结论</strong></p> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;">我们经历了 async/await 的过程,并在 Golang 中实现了一个简单的版本。我鼓励您进一步研究 async/await,看看它如何更好的让代码库便于易读。</p> <p data-tool="mdnice编辑器" style="font-size: 16px; padding-top: 8px; padding-bottom: 8px; margin: 0; line-height: 26px; color: black;"><em style="font-style: italic; color: black;">作者:MD Ahad Hasan | 译者:Roc | 转自:InfoQ</em></p> </section> [拿走不谢!Python 3.9 官方中文文档,限时领!] (http://dwz.date/dE6v)

[限时!速领!14张高清Python速查表,效率提升必备!] (http://dwz.date/dE6w)

[GitHub标星3W+,80个Python案例,带你轻松玩转Python学习!] (http://dwz.date/dE64)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!