Revel 框架不提供原生异步控制器,但可通过 AsyncTask 封装 goroutine 实现安全异步;直接 go func() 会导致资源泄漏、panic 静默失败、结果不可知;必须避免在后台 goroutine 中访问请求上下文 c,推荐用值传递或独立队列通信。

Revel 框架本身不提供原生异步控制器,但你完全可以在不阻塞 HTTP 请求的前提下,安全地把耗时逻辑(比如第三方 API 调用、文件生成、复杂查询)挪到后台 goroutine 执行。关键不是“能不能开 goroutine”,而是“怎么确保它不泄漏、不丢失、不干扰请求生命周期”。
为什么不能直接 go func() { ... }()?
看似简单的一行 go 会埋下三个隐患:
- goroutine 可能比请求生命周期更长,导致内存/连接/资源无法释放;
- 如果任务 panic,没有 recover 机制,会静默失败;
- 无法感知任务是否完成、是否出错,后续日志、监控、重试都无从谈起。
Revel 的拦截器链(intercept.go)是串行执行的,主线程一旦返回 revel.Result,请求上下文就进入销毁流程 —— 此时后台 goroutine 若还在读写 c.Params 或 c.RenderArgs,就是典型的竞态访问。
用 AsyncTask 封装 goroutine + channel 等待结果
推荐在控制器中显式构造一个带超时和错误传递的异步任务封装:
type AsyncTask struct {
done chan error
panic chan string
}
<p>func NewAsyncTask() *AsyncTask {
return &AsyncTask{
done: make(chan error, 1),
panic: make(chan string, 1),
}
}</p><p>func (a *AsyncTask) Run(f func() error) {
go func() {
defer func() {
if r := recover(); r != nil {
a.panic <- fmt.Sprintf("%v", r)
}
}()
a.done <- f()
}()
}</p><p>func (a *AsyncTask) Wait(timeout time.Duration) error {
select {
case err := <-a.done:
return err
case msg := <-a.panic:
return errors.New("panic in async task: " + msg)
case <-time.After(timeout):
return errors.New("async task timeout")
}
}
在控制器里这样用:
func (c *ProductController) Show() revel.Result {
task := NewAsyncTask()
task.Run(func() error {
// 这里放耗时操作,比如:
_, _ = api.GetLogisticsInfo(c.Params.Get("order_id"))
return nil
})
<pre class="brush:php;toolbar:false;">// 主线程继续做轻量工作,比如查主数据
product, _ := models.GetProduct(c.Params.Get("id"))
// 等待后台任务(可选:也可不等,纯 fire-and-forget)
if err := task.Wait(2 * time.Second); err != nil {
revel.WARN.Printf("logistics fetch failed: %v", err)
}
c.ViewArgs["product"] = product
return c.Render()}
真正后台执行(不等待):用 Revel 的 App.OnAppStart 注册全局 worker
如果任务完全不需要返回结果给当前请求(例如发通知、写审计日志、触发清理),应该彻底剥离出请求上下文,走独立 worker 模式:
- 在
app/init.go的init()函数里,用revel.App.OnAppStart启动常驻 goroutine; - 用
chan或sync.Map做内存队列(小流量)或对接 Redis 队列(生产); - 避免在 goroutine 内引用任何
*revel.Controller实例或其字段 —— 它们只在请求周期内有效。
示例启动代码:
var taskQueue = make(chan map[string]interface{}, 100)
<p>func init() {
revel.InterceptMethod((<em>App).Before, revel.BEFORE)
revel.App.OnAppStart(func() {
go func() {
for task := range taskQueue {
// 处理 task,不依赖任何 c.</em>
sendNotification(task)
}
}()
})
}
控制器中只需投递:
taskQueue <- map[string]interface{}{
"type": "email",
"to": c.Params.Get("email"),
}
最易被忽略的是:Revel 的 revel.Result 返回后,c 对象随时可能被 GC,所有对它的引用(包括闭包捕获)都必须在返回前完成。异步 ≠ 放飞自我,而是明确划分责任边界 —— 请求线程负责响应,后台 goroutine 负责执行,两者之间只通过值传递或独立队列通信。


















