2 回答
TA贡献1801条经验 获得超15个赞
没有办法从该 goroutine外部的任何代码中强制拆除该 goroutine。
因此,真正中断处理的唯一方法是定期检查客户端是否消失(或者是否有另一个信号来停止处理)。
基本上这相当于构建你的处理程序是这样的
func ReallyLongFunction(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
done := r.Context().Done()
// Check wheteher we're done
// do some small piece of stuff
// check whether we're done
// do another small piece of stuff
// …rinse, repeat
})
现在,检查是否有内容写入通道但不阻塞操作的方法是使用“使用默认值选择”习惯用法:
select {
case <- done:
// We're done
default:
}
当且仅当done被写入或被关闭(上下文就是这种情况)时,此状态才会执行“// We're done”块中的代码,否则default执行分支中的空块。
所以我们可以将其重构为类似的东西
func ReallyLongFunction(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
done := r.Context().Done()
closed := func () bool {
select {
case <- done:
return true
default:
return false
}
}
if closed() {
return
}
// do some small piece of stuff
if closed() {
return
}
// do another small piece of stuff
// …rinse, repeat
})
停止在 HTTP 处理程序中启动的外部进程
为了解决OP的评论......
该os/exec.Cmd
类型具有Process
字段,该字段具有类型os.Process
并且该类型支持Kill
强制关闭正在运行的进程的方法。
唯一的问题是exec.Cmd.Run
阻塞直到进程退出,因此正在执行的 goroutine 无法执行其他代码,并且如果exec.Cmd.Run
在 HTTP 处理程序中调用,则无法取消它。
如何最好地处理以这种异步方式运行程序在很大程度上取决于进程本身的组织方式,但我会这样滚动:
在处理程序中,准备进程,然后使用
exec.Cmd.Start
(而不是Run
)启动它。检查已返回的错误值
Start
:如果是,则nil
进程已成功启动。否则,以某种方式将失败传达给客户端并退出处理程序。一旦知道流程已经启动,该
exec.Cmd
值的一些字段就会填充与流程相关的信息;特别感兴趣的是Process
类型为 的字段os.Process
:该类型具有Kill
可用于强制关闭进程的方法。启动一个 goroutine 并向其传递该
exec.Cmd
值和某种合适类型的通道(见下文)。该 Goroutine 应该调用
Wait
它,一旦返回,它应该通过该通道将该事实传达回原始 Goroutine。到底要通信什么,是一个悬而未决的问题,因为它取决于您是否想要收集进程写入其标准输出和错误流的内容和/或可能是与进程活动相关的一些其他数据。
发送数据后,该 goroutine 退出。
主 goroutine(执行处理程序)应该
exec.Cmd.Process.Kill
在检测到处理程序应该终止时调用。终止进程最终会解除对 goroutine 的阻塞,该 goroutine在进程退出时
Wait
以相同的值执行。exec.Cmd
终止进程后,处理程序 goroutine 会在通道上等待监听该进程的 goroutine 的回复。处理程序对该数据执行某些操作(可能是记录它或其他什么)并退出。
TA贡献1810条经验 获得超4个赞
您应该从内部取消 goroutine,因此对于长时间的计算任务,您可以提供检查点,以停止并检查取消:
这是服务器的测试代码,该代码具有例如长计算任务和取消检查点:
package main
import (
"fmt"
"io"
"log"
"net/http"
"time"
)
func main() {
http.HandleFunc(`/`, func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log.Println("wait a couple of seconds ...")
for i := 0; i < 10; i++ { // long calculation
select {
case <-ctx.Done():
log.Println("Client closed the connection:", ctx.Err())
return
default:
fmt.Print(".")
time.Sleep(200 * time.Millisecond) // long calculation
}
}
io.WriteString(w, `Hi`)
log.Println("Done.")
})
log.Println(http.ListenAndServe(":8081", nil))
}
这是超时的客户端代码:
package main
import (
"io/ioutil"
"log"
"net/http"
"time"
)
func main() {
log.Println("HTTP GET")
client := &http.Client{
Timeout: 1 * time.Second,
}
r, err := client.Get(`http://127.0.0.1:8081/`)
if err != nil {
log.Fatal(err)
}
defer r.Body.Close()
bs, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatal(err)
}
log.Println("HTTP Done.")
log.Println(string(bs))
}
您可以使用普通浏览器检查是否取消,或者关闭、刷新、断开连接,或者...,取消。
- 2 回答
- 0 关注
- 123 浏览
添加回答
举报