扭曲多个并发或异步流

2 投票
1 回答
740 浏览
提问于 2025-04-18 01:29

我正在用Python写一个应用程序,使用twisted.web框架来通过HTML5播放视频。

视频是通过static.File('pathtovideo').render_GET()来提供的。问题是一次只能播放一个视频,因为这会占用整个进程。

有没有办法让视频播放变成异步的,或者说不阻塞进程,这里用哪个词都可以。

我试过使用deferToThread,但这仍然占用了进程。

这是我目前使用的类,其中Movie是一个ORM表,mid只是一个任意行的ID。

class MovieStream(Resource):
    isLeaf=True

    def __init__(self, mid):
        Resource.__init__(self)
        self.mid = mid

    def render_GET(self, request):
        movie = Movie.get(Movie.id == self.mid)
        if movie:
             defered = deferToThread(self._start_stream, path=movie.source), request=request)
             defered.addCallback(self._finish_stream, request)
             return NOT_DONE_YET
        else:
            return NoResource()
`
     def _start_stream(self, path, request):
         stream = File(path)
         return stream.render_GET(request)

     def _finish_stream(self, ret, request):
         request.finish()

1 个回答

3

这段代码中看起来像是阻塞的部分其实是 Movie.get 这个调用。

deferToThread 来调用 _start_stream 是不对的,因为 _start_stream 使用了 Twisted 的 API(比如 File 以及 File.render_GET 使用的东西),在反应器线程之外使用 Twisted 的 API 是不合法的(换句话说,就是在用 deferToThread 调用的函数里不能使用这些 API)。

幸运的是,你只需要删除 deferToThread 的使用就能修复这个错误。

要解决 Movie.get 阻塞的问题,你需要找到一种异步访问数据库的方法。也许可以使用 deferToThread(Movie.get, Movie.id == self.mid),前提是实现 Movie.get 的数据库库是线程安全的。

顺便提一下,你也可以通过将数据库查找逻辑提前到资源遍历的层级更高的位置来避免 render_GET 的一些麻烦。

举个例子,我想你的 URL 可能像这样 /foo/bar/<movie id>。在这种情况下,/foo/bar 这个资源会被请求 <movie id> 的子资源。如果你这样实现查找:

from twisted.web.resource import Resource
from twisted.web.util import DeferredResource

class MovieContainer(Resource):
    def getChild(self, movieIdentifier):
        condition = (Movie.id == movieIdentifier)
        getting = deferToThread(Movie.get, condition)
        return DeferredResource(getting)

(假设 Movie.get 是线程安全的)那么你基本上就完成了。

资源遍历将以 DeferredResource(getting) 构造的对象结束,当这个对象被渲染时,它会处理等待 getting 有结果(在行话中就是让 Deferred "触发")并调用正确的方法,比如 render_GET,来为请求生成响应。

撰写回答