Azure SDK for go を使っていると、突然 Context
というものが出てきて意味がわからなかったので調べて見た。
公式のリファレンスを読んだのちに
次のブログがとてもわかりやすかった。
ざっとまとめると、Context は、APIのサーバーやクライアントを使うときに、コンテキストを提供してキャンセルや、タイムアウト、値を渡したり出来る仕組み。
Context 構造体の持っている属性や関数に関しては上記のブログに書いてあるので、ここでは書かないけど、何もなし(Background) キャンセル、タイムアウト、値渡しなどの関数が存在する。Context の構造体自体はキャンセルのメソッドは提供しないが、Context はデコレーターの様になっておりラップが可能になっている。上記のブログのサンプルを拡張して見た。
基本的な使い方
go routine
などを呼び出す元の方でオブジェクトを生成する。context.Background()
は空のコンテキストを生成する。
ctx := context.Background()
これをgo routine
を実行時に引き渡す。
go infiniteLoop(ctx)
go routine
の方では、それを受け取る。この例ではループが回るが、キャンセルされたら、Done()
が呼ばれるので、メッセージを出力する。
ポイントは、コンテキストオブジェクトは、デコレータの様な使い方をするところ。この例では、渡ってきたのは、空のContext
が渡ってきているが、ここで、context.WithCancel(ctx)
の様にラップしてキャンセル機能を追加している。
func infiniteLoop(ctx context.Context) {
innerCtx, cancel := context.WithCancel(ctx)
defer cancel()
for {
fmt.Println("Waiting for time out")
select {
case <-innerCtx.Done():
fmt.Println("Exit now!")
return
default:
}
}
}
タイムアウトの実装
たとえばタイムアウトを追加したければ、本体側でこんな感じで書けば良い。キャンセルされたらキャンセルの関数が呼ばれる様に defer
関数を使っておく。
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
しっかりと 5秒後にキャンセルされ、Done
が呼ばれる。
Key/Value 値を設定する
Context はキーバリューも保持できる様になっている。これもデコレータ的な使い方をする。
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
ctx = context.WithValue(ctx, "message", "hi")
go routine
側でこの値を取りたければこんな感じにすると良い。KeyValue の型はinterface {}
なので、型変換が必要。
fmt.Println("message:", ctx.Value("message").(string))
コード全体
こちらにコードを置いておいた
全体を載せておく。
package main
import (
"context"
"fmt"
"time"
)
func infiniteLoop(ctx context.Context) {
innerCtx, cancel := context.WithCancel(ctx)
defer cancel()
for {
fmt.Println("Waiting for time out")
//time.Sleep(time.Second)
// If I put sleep in here, fmt.Println doesn't output the "Exit now!"
select {
case <-innerCtx.Done():
fmt.Println("Exit now!")
fmt.Println("message:", ctx.Value("message").(string))
return
default:
}
}
}
func main() {
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
ctx = context.WithValue(ctx, "message", "hi")
defer cancel()
go infiniteLoop(ctx)
select {
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
次のステップ
Context は理解できたと思うが、上記のサンプルコードには一点わからないことがある。上記のもので、time.Sleep(time.Second)
を有効にすると、キャンセル時に、fmt.Println("Exit now!")
が実行されない。なんでだろう。