使用 Go 实现 Async/Await 模式
概述
Golang 是一种并发编程语言。它具有强大的特性,如 和 ,可以很好地处理异步任务。另外, 不是 OS 线程,这就是为什么您可以在不增加开销的情况下根据需要启动任意数量的 的原因,它的堆栈大小初始化时仅 2KB。那么为什么要 呢? 是一种很好的语言特点,它为异步编程提供了更简单的接口。
它是如何工作的?
从 F# 开始,然后是 C#,到现在 Python 和 Javascript 中, 是一种非常流行的语言特点。它简化了异步方法的执行结构并且读起来像同步代码。对于开发人员来说更容易理解。让我们看看 c# 中的一个简单示例 是如何工作的。
static async Task Main(string[] args)
{
Console.WriteLine("Let's start ...");
var done = DoneAsync();
Console.WriteLine("Done is running ...");
Console.WriteLine(await done);
}
static async Task DoneAsync()
{
Console.WriteLine("Warming up ...");
await Task.Delay(3000);
Console.WriteLine("Done ...");
return 1;
}
当程序运行时,我们的 函数将被执行。我们有异步函数 。我们使用 方法停止执行代码 3 秒钟。Delay 本身是一个异步函数,所以我们用 来调用它。
只阻塞异步函数内的代码执行
在 函数中,我们不使用 来调用 。但 开始执行后,只有当我们 它的时候,我们才会得到结果。执行流程如下所示:
Let's start ...
Warming up ...
Done is running ...
Done ...
1
对于异步执行,这看起来非常简单。让我们看看如何使用 Golang 的 和 来做到这一点。
func DoneAsync() chan int {
r := make(chan int)
fmt.Println("Warming up ...")
go func() {
time.Sleep(3 * time.Second)
r <- 1
fmt.Println("Done ...")
}()
return r
}
func main () {
fmt.Println("Let's start ...")
val := DoneAsync()
fmt.Println("Done is running ...")
fmt.Println(<- val)
}
在这里, 异步运行并返回一个 。执行完异步任务后,它会将值写入 。在 函数中,我们调用 并继续执行后续操作,然后从返回的 读取值。它是一个阻塞调用,等待直到将值写入 ,并在获得值后将其输出到终端。
Let's start ...
Warming up ...
Done is running ...
Done ...
1
我们看到,我们实现了与 C# 程序相同的结果,但它看起来不像 那样优雅。尽管这确实不错,但是我们可以使用这种方法轻松地完成很多细粒度的事情,我们还可以用一个简单的结构和接口在 Golang 中实现 关键字。让我们试试。
实现 Async/Await
完整代码可在项目链接中找到(在文章开始的地方)。要在 Golang 中实现 ,我们将从一个名为 的包目录开始。项目结构看起来是这样的。
.
├── async
│ └── async.go
├── main.go
└── README.md
在 文件中,我们编写了可以处理异步任务最简单的 接口。
package async
import "context"
// Future interface has the method signature for await
type Future interface {
Await() interface{}
}
type future struct {
await func(ctx context.Context) interface{}
}
func (f future) Await() interface{} {
return f.await(context.Background())
}
// Exec executes the async function
func Exec(f func() interface{}) Future {
var result interface{}
c := make(chan struct{})
go func() {
defer close(c)
result = f()
}()
return future{
await: func(ctx context.Context) interface{} {
select {
case <-ctx.Done():
return ctx.Err()
case <-c:
return result
}
},
}
}
这里发生的事情并不多,我们添加了一个具有 方法标识的 接口。接下来,我们添加一个 结构,它包含一个值,即 函数的函数标识。现在 通过调用自己的 函数来实现 接口的 方法。
接下来在 函数中,我们在 中异步执行传递的函数。然后返回 函数。它等待 关闭或 读取。基于最先发生的情况,它要么返回错误,要么返回作为接口的结果。
现在,有了这个新的 包,让我们看看如何更改当前的 go 代码:
func DoneAsync() int {
fmt.Println("Warming up ...")
time.Sleep(3 * time.Second)
fmt.Println("Done ...")
return 1
}
func main() {
fmt.Println("Let's start ...")
future := async.Exec(func() interface{} {
return DoneAsync()
})
fmt.Println("Done is running ...")
val := future.Await()
fmt.Println(val)
}
乍一看,它看起来干净得多,这里我们没有显式地使用 或 。我们的 函数已更改为完全同步的性质。在 函数中,我们使用 包的 方法来处理 。在开始执行 。控制流返回到可以执行其他代码的 函数中。最后,我们对 进行阻塞调用并回读数据。
现在,代码看起来更加简单易读。我们可以修改我们的 async 包从而能在 Golang 中合并许多其他类型的异步任务,但在本教程中,我们现在只坚持简单的实现。
结论
我们经历了 的过程,并在 Golang 中实现了一个简单的版本。我鼓励您进一步研究 ,看看它如何更好的让代码库便于易读。
译自:https://hackernoon.com/asyncawait-in-golang-an-introductory-guide-ol1e34sg
