Flask-socketio在后台线程复制文件时错过事件



(完整的测试应用程序在github: https://github.com/olingerc/socketio-copy-large-file)

我正在使用Flask和Flask- socketio插件。我的客户端可以通过websocket请求服务器复制文件,但是当文件正在复制时,我希望客户端能够与服务器通信,要求它做其他事情。我的解决方案是在后台线程中运行复制进程(shutil)。下面是函数:

def copy_large_file():
    source = "/home/christophe/Desktop/largefile"
    destination = "/home/christophe/Desktop/largefile2"
    try:
        os.remove(destination)
    except:
        pass
    print("Before copy")
    socketio.emit('my_response',
                  {'data': 'Thread says: before'}, namespace='/test')
    shutil.copy(source, destination)
    print("After copy")
    socketio.emit('my_response',
                  {'data': 'Thread says: after'}, namespace='/test')

我观察到以下行为:当使用本机socketio方法启动函数时:

socketio.start_background_task(target=copy_large_file)

在复制大文件时,所有传入事件都将延迟,直到文件完成并启动下一个文件。我猜shutil没有释放GIL或类似的东西,所以我测试了threading:

thread = threading.Thread(target=copy_large_file)
thread.start()

相同的行为。也许多处理?

thread = multiprocessing.Process(target=copy_large_file)
thread.start()

啊!这可以工作,并且通过copy_large_file函数中的socketio发出的信号被正确接收。但是:如果用户开始复制一个非常大的文件,关闭浏览器并在2分钟后返回,套接字不再连接到相同的套接字"session?",因此不再接收从后台进程发出的消息。

我猜主要的问题是:我如何在后台复制大文件而不阻塞flask- sockettio,但仍然能够从后台进程中向客户端发出信号。

测试应用程序可以用来重现行为:

    克隆https://github.com/olingerc/socketio-copy-large-file
  • <
  • 安装需求/gh>
  • copy_file函数中的选择方法(第42行)
  • 从。/app.py
  • 开始

浏览器:

  • go to localhost:5000
  • 点击复制文件
  • 在复制文件时点击Ping发送消息
  • 还要注意来自后台线程的其他信号

你问了两个不同的问题。

首先,让我们讨论一下文件的实际复制。

看起来您正在为服务器使用eventlet。虽然这个框架为网络I/O函数提供了异步替代,但是以非阻塞方式执行磁盘I/O要复杂得多,特别是在Linux上(这里有一些关于这个问题的信息)。因此,正如您所注意到的,即使使用标准库打了补丁,对文件进行I/O也会导致阻塞。顺便说一下,gevent也是如此。

对文件执行非阻塞I/O的典型解决方案是使用线程池。使用eventlet, eventlet.tpool.execute函数可以做到这一点。基本上,不直接调用copy_large_file(),而是调用tpool.execute(copy_large_file)。这将使应用程序中的其他绿色线程能够在另一个系统线程中进行复制时运行。顺便说一下,您使用另一个进程的解决方案也是有效的,但根据您需要执行这些副本的次数和频率,这可能是多余的。

你的第二个问题是关于"记住"启动长文件复制的客户端,即使浏览器关闭并重新打开。

这确实是应用程序需要通过存储恢复返回客户机所需的状态来处理的事情。假定您的客户机有一种方法来识别您的应用程序,可以使用令牌或其他标识。当服务器启动其中一个文件副本时,它可以为该操作分配一个id,并将该id存储在与请求该操作的客户机相关联的数据库中。如果客户端离开,然后返回,您可以发现是否有任何正在进行的文件副本,并通过这种方式将客户端同步到关闭浏览器之前的方式。

希望这对你有帮助!

最新更新