context.Background() Context и TODO() Context. Они идентичны и обе возвращают пустой контекст. Background(). Функцию TODO() выбирают в тех случаях, когда ещё непонятно, как получить нужный контекст: например, внутри функции используется контекст, который должен передаваться в её параметре, но пока такой параметр не определён.WithCancel() — дочерний контекст можно отменить вызовом функции;WithDeadline() — дочерний контекст отменяется в указанное время;WithTimeout() — дочерний контекст отменяется по тайм-ауту;WithValue() — дочерний контекст содержит пару «ключ-значение».ctx1 будет отменён контекст ctx3, даже если указанный для него тайм-аут ещё не истёк. WithCancel(parent Context) (ctx Context, cancel CancelFunc). Она вернёт новый дочерний контекст и функцию cancel(), с помощью которой можно отменить этот дочерний контекст и всё его дерево.cancel() нужно обязательно выполнить в коде, иначе сборщик мусора не удалит созданный дочерний контекст и произойдёт утечка памяти. Чтобы отменить контекст в конце выполнения функции, часто используют конструкцию defer cancel().Done(), который возвращает канал пустых структур. При отмене контекста этот канал закроется, и операция чтения <-ctx.Done() будет успешной.<-ctx.Done(), то выполнение функции в этом месте остановится до закрытия канала. Если нужно дождаться наступления события, но при этом отреагировать на отмену контекста (если он отменится раньше), можно использовать оператор select. Он позволяет ожидать наступления первого из нескольких событий. select похож на switch, только он проверяет не условия, а события: получение и запись значений или закрытие канала. Например:select {
case <-ctx.Done():
fmt.Println("Контекст отменён, следовательно, канал, " +
"возвращаемый из Done(), закрыт")
case x: <-ch1:
fmt.Println("Канал ch1 закрыт или в него записано значение, " +
"прочитанное значение:", x)
case <-ch2:
fmt.Println("Канал ch2 закрыт или в него записано значение, " +
"само значение игнорируется")
default:
fmt.Println("Ни одно из событий не произошло")
} select указана ветка default, она начинает выполняться, если не наступило ни одно событие case. В этом случае нет задержки на ожидание события.select ни одно из событий не наступило и не будет default, выполнение кода остановится до наступления как минимум одного из событий.package main
import (
"context"
"fmt"
"time"
)
func doSomething(ctx context.Context) {
fmt.Println("Начало работы")
for i := 0; i < 5; i++ {
select {
case <-ctx.Done():
fmt.Println("Прервали работу")
return
default:
fmt.Println(i)
time.Sleep(1 * time.Second)
}
}
fmt.Println("Конец работы")
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// через 1,5 секунды вызываем cancel для отмены операции
// если cancel вызовется два раза, это не приведёт к ошибке
time.AfterFunc(1500*time.Millisecond, cancel)
doSomething(ctx)
} Начало работы
0
1
Прервали работу Context.Err() error. Если контекст отменён, то вернётся ошибка. Функцию doSomething() из примера можно реализовать, используя этот метод:func doSomething(ctx context.Context) {
fmt.Println("Начало работы")
for i := 0; i < 5; i++ {
if ctx.Err() != nil {
fmt.Println("Прервали работу")
return
}
fmt.Println(i)
time.Sleep(1 * time.Second)
}
fmt.Println("Конец работы")
} ctx.WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) создаёт дочерний контекст, который отменяется самостоятельно после указанного интервала времени. Если требуется завершить контекст в определённый момент, нужно использовать функцию WithDeadline(parent Context, d time.Time) (Context, CancelFunc).ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
// эквивалентно
ctx, cancel := context.WithDeadline(context.Background(),
time.Now().Add(2*time.Second)) WithCancel(), возвращают дочерний контекст и функцию отмены контекста. Чтобы узнать, как именно отменили контекст, можно получить ошибку методом ctx.Err(). Этот метод отдаёт одно из значений:context.Canceled — контекст отменён функцией cancel();context.DeadlineExceeded — контекст отменён автоматически по истечении времени.CancelCauseFunc func(cause error) и две функции:Cause(c Context) error
WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) WithCancelCause(), при вызове функции cancel() требуется указать ошибку. Cause() позволяет узнать подробности отмены контекста. Если контекст отменён с указанием ошибки, то Cause() возвращает эту ошибку. В противном случае она возвращает то же, что и ctx.Err().WithTimeout():package main
import (
"context"
"fmt"
"time"
)
func doSomething(ctx context.Context) {
fmt.Println("Начало работы")
for i := 0; i < 5; i++ {
select {
case <-ctx.Done():
return
default:
fmt.Println(i)
time.Sleep(1 * time.Second)
}
}
fmt.Println("Конец работы")
}
func main() {
// если указать интервал 1500 миллисекунд, то doSomething
// успеет выполниться полностью
ctx, cancel := context.WithTimeout(context.Background(), 1500*time.Millisecond)
defer cancel()
doSomething(ctx)
// проверяем, как завершился контекст
switch ctx.Err() {
case context.Canceled:
fmt.Println("Прервали работу")
case context.DeadlineExceeded:
fmt.Println("Истекло время работы")
}
} WithValue(parent Context, key, val any) Context создаёт дочерний контекст с указанным ключом и значением. Для получения значения применяют метод Value(key any) any, а поиск значения происходит в обратную сторону по всей цепочке контекстов. Если значение не найдено, возвращается nil.package main
import (
"context"
"fmt"
)
func doValue(ctx context.Context) {
fmt.Println(ctx.Value("group"), ctx.Value("name"))
}
func main() {
ctx := context.WithValue(context.Background(), "group", "admin")
ctxuser := context.WithValue(ctx, "name", "Dima")
doValue(ctxuser)
} admin Dima // NewRequestWithContext создаёт запрос с контекстом
func NewRequestWithContext(ctx context.Context, method, url string,
body io.Reader) (*Request, error)
// WithContext устанавливает контекст для запроса
func (r *Request) WithContext(ctx context.Context) *Request
// Context возвращает контекст запроса
func (r *Request) Context() context.Context http.Request:NewRequest() используем функцию NewRequestWithContext();WithContext() для уже созданного запроса.package main
import (
"context"
"io"
"log"
"net/http"
"time"
)
func main() {
// создаём контекст с максимальным временем запроса
ctx, cancel := context.WithTimeout(context.Background(),
time.Duration(time.Millisecond*500))
defer cancel()
// создаём запрос к серверу http://httpbin.org, ответ будет возвращаться
// с задержкой в одну секунду, которая указана в запросе /delay/1
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"http://httpbin.org/delay/1", nil)
// эквивалентно
// req, err := http.NewRequest(http.MethodGet,
// "http://httpbin.org/delay/1", nil)
// ...
// req.WithContext(ctx)
if err != nil {
log.Fatal(err)
}
c := &http.Client{}
res, err := c.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
out, err := io.ReadAll(res.Body)
if err != nil {
log.Fatal(err)
}
log.Println(string(out))
} 2022/11/28 17:24:19 Get "http://httpbin.org/delay/1": context deadline exceeded
exit status 1 context.Background() только для создания родительского контекста, он находится на самом верху в иерархии контекстов.nil в качестве контекста. Если не знаете, что передать, используйте context.TODO().context.WithValue(). Значения лучше передавать через параметры функций.cancel(). Отмена контекста должна происходить в функции, которая его порождает.context.sqlx.net/http.runtime/trace.