Flask-socketio在后台线程

时间:2016-09-16 10:01:50

标签: python multithreading multiprocessing eventlet 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或类似的东西,所以我测试了线程:

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

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

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

啊!这是有效的,并且正确接收了通过copy_large_file函数中的socketio发出的信号。 但: 如果用户开始复制一个非常大的文件,关闭他们的浏览器并在2分钟后回来,套接字不再连接到相同的socketio“会话”?因此不再接收后台进程发出的消息。

我想主要的问题是:如何在不阻止flask-socketio的情况下在后台复制大文件,但仍然能够在后台进程中向客户端发出信号。

测试应用可用于重现行为:

在浏览器中:

  • 转到localhost:5000
  • 点击复制文件
  • 单击Ping以在复制文件时发送消息
  • 还要关注来自后台线程的其他信号

1 个答案:

答案 0 :(得分:2)

你问两个不同的问题。

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

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

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

你的第二个问题与"记住"即使浏览器已关闭并重新打开,也会启动长文件副本的客户端。

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

希望这有帮助!