目录


1.context.WithTimeout + time.After

func AsyncCall() {
 	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
 	defer cancel()
 	
 	go func(ctx context.Context) {
 		// 发送HTTP请求
 	}()

 	select {
		 case <-ctx.Done():
 			fmt.Println("call successfully!!!")
			 return
		 case <-time.After(time.Duration(time.Millisecond * 900)):
 			fmt.Println("timeout!!!")
 			return
		 }
}

通过context的WithTimeout设置一个有效时间为800毫秒的context

该context会在耗尽800毫秒后或者方法执行完成后结束,结束的时候会向通道ctx.Done发送信号

Done通道负责监听context啥时候结束,如果在time.After设置的超时时间到了,你还没完事,那我就不等了,执行超时后的逻辑代码。

还要加上这个time.After呢?

这是因为该方法内的context是自己申明的,可以手动设置对应的超时时间,但是在大多数场景,这里的ctx是从上游一直传递过来的,对于上游传递过来的context还剩多少时间,我们是不知道的,所以这时候通过time.After设置一个自己预期的超时时间就很有必要了。

2.context.WithTimeout + time.NewTimer

func AsyncCall() {
 	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond * 800))
 	defer cancel()
 	timer := time.NewTimer(time.Duration(time.Millisecond * 900))

 	go func(ctx context.Context) {
 		// 发送HTTP请求
 	}()

 	select {
 		case <-ctx.Done():
 			timer.Stop()
			timer.Reset(time.Second)
 			fmt.Println("call successfully!!!")
 			return
 		case <-timer.C:
 			fmt.Println("timeout!!!")
 			return
 	}
}

这里的主要区别是将time.After换成了time.NewTimer,也是同样的思路如果接口调用提前完成,则监听到Done信号,然后关闭定时器。否则的话,会在指定的timer即900毫秒后执行超时后的业务逻辑。

3.channel + time.After/time.NewTimer

func AsyncCall() {
 	ctx := context.Background()
 	done := make(chan struct{}, 1)

 	go func(ctx context.Context) {
 		// 发送HTTP请求
 		done <- struct{}{}
 	}()

 	select {
 		case <-done:
 			fmt.Println("call successfully!!!")
 			return
 		case <-time.After(time.Duration(800 * time.Millisecond)):
 			fmt.Println("timeout!!!")
 		return
 	}
}

这里主要利用通道可以在协程之间通信的特点,当调用成功后,向done通道发送信号。

监听Done信号,如果在time.After超时时间之前接收到,则正常返回,否则走向time.After的超时逻辑,执行超时逻辑代码。

这里使用的是通道和time.After组合,也可以使用通道和time.NewTimer组合。