Остановка работающей функции с использованием времени ожидания контекста в Golang - PullRequest
1 голос
/ 05 апреля 2020

Я хочу использовать контекст в golang, который будет использоваться для отмены по истечении времени ожидания.

Код:

package main

import "fmt"
import "time"
import "context"

func F(ctx context.Context) error {
  ctx, cancel := context.WithTimeout(ctx,3*time.Second)
  defer cancel()
  for i:=0;i<10;i++ {
    time.Sleep(1 * time.Second)
    fmt.Println("No: ",i)
  }
  select {
    case <-ctx.Done():
      fmt.Println("TIME OUT")
      cancel()
      return ctx.Err()
    default:
      fmt.Println("ALL DONE")
      return nil
  }
}

func main() {
  ctx := context.Background()
  err := F(ctx)
  if err != nil {
    fmt.Println(err)
  }else {
    fmt.Println("Success")
  }
}

Ожидание: приведенный выше код должен прекратить работу l oop на счетчике 2, потому что тайм-аут составляет 3 секунды, и цикл запускается по 1 секунде каждый. Так что я ожидаю что-то вроде этого:

No:  0
No:  1
No:  2
TIME OUT
context deadline exceeded

Фактически: что на самом деле происходит, то l oop продолжает работать до конца sh, даже если время ожидания контекста и прослушиватель select перехватывают это на <-ctx.Done() , Этот код печатает это:

No:  0
No:  1
No:  2
No:  3
No:  4
No:  5
No:  6
No:  7
No:  8
No:  9
TIME OUT
context deadline exceeded

Как остановить выполнение функции по истечении времени ожидания?

1 Ответ

1 голос
/ 05 апреля 2020

context.Context может только передать сообщение о том, что произошел тайм-аут или отмена. Он не может фактически остановить любые процедуры (подробности см. отмена операции блокировки в Go). Сама программа отвечает за проверку тайм-аута и отмены, и прерывает работу раньше.

У вас есть все oop, которое безоговорочно повторяется 10 раз и что-то печатает. И вы проверяете тайм-аут только после l oop.

. Вы должны переместить проверку контекста в l oop:

func F(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()
    for i := 0; i < 10; i++ {
        select {
        case <-ctx.Done():
            fmt.Println("TIME OUT")
            cancel()
            return ctx.Err()
        default:
            time.Sleep(1 * time.Second)
            fmt.Println("No: ", i)
        }
    }
    fmt.Println("ALL DONE")
    return nil
}

С этим изменением результат будет ( попробуйте это на игровой площадке Go ):

No:  0
No:  1
No:  2
No:  3
TIME OUT
context deadline exceeded

Примечание: если вы видите напечатанное "No: 3", это может произойти или не произойти, поскольку любая итерация занимает 1 секунду, а время ожидания 3 секунды = 3 * задержка итерации, поэтому, будет ли таймаут первым или четвертая итерация начнется первой, будет "racy". Если вы уменьшите время ожидания до 2900 мс, "No: 3" не будет напечатано.

...