基于异步回调的API上的同步API包装器

时间:2019-07-19 09:07:32

标签: go asynchronous design-patterns

我在项目中使用了pion/webrtc Go库,发现该库提供的基于回调的API(反映了WebRTC的JavaScript API)在Go中使用时很尴尬。 >

例如,执行以下操作

conn.OnTrack(func(...) { ... })
conn.OnICEConnectionStateChange(func(...) { ... })

在JavaScript中是典型的,但是在Go语言中,这有一些问题:

  • 如果回调是并行调用的,则此API可以轻松引入数据竞争。
  • 基于回调的API会传播到代码库的其他部分,并使所有内容都接受回调。

在Go中处理这种情况的常规方法是什么?我是Go的新手,我读到Go中首选同步API,因为Goroutines很便宜。因此,也许一种可能的设计是使用一个通道来同步回调:

msgChan := make(chan Msg)
// or use a separate channel for each type of event?

conn.OnTrack(func(...) {
  msgChan <- onTrackMsg
})
conn.OnICEConnectionStateChange(func(...) {
  msgChan <- onStateChangeMsg
})

for {
  msg := <-msgChan
  // do something depending on the type of msg
}

我认为强制与通道同步基本上模仿了JavaScript的单线程性质。

无论如何,人们通常如何在Go中建模事件驱动的工作流程?

1 个答案:

答案 0 :(得分:1)

不需要频道。只需将异步/回调代码包装在等待响应的单个函数中,然后使用WaitGroup(您可以在此处使用通道,但使用WaitGroup要容易得多):

func DoSomething() (someType, error) {
    var result SomeType
    var err error
    wg := sync.WaitGroup{}
    wg.Add(1)
    StartAsyncProcess(func() {
        // This is the call back that gets called eventually
        defer wg.Done()
        result = /* Set the result */
        err = /* and/or set the error */
    })
    wg.Wait() // Wait until the callback is called, and exits
    return result, err  // And finally return our values
}

如果您的回调依赖或修改共享状态,则可能需要/希望在回调中添加其他锁定或同步。