web-dev-qa-db-ja.com

Python-スレッドからのFlask-SocketIO送信メッセージ:常に機能するとは限らない

私はクライアントからメッセージを受け取る状況にあります。そのリクエストを処理する関数(@ socketio.on)内で、重い処理が行われる関数を呼び出します。これによりメインスレッドがブロックされることはなく、作業が完了するとクライアントに通知されます。したがって、私は新しいスレッドを開始します。

今、私は本当に奇妙な振る舞いに遭遇します:メッセージがクライアントに届かないただし、コードはメッセージが送信される特定の場所に到達します。さらに驚くべきことは、メッセージがクライアントに送信されることを除いてスレッドで何も起こらない場合、答えは実際にクライアントへの経路を見つけるという事実です。

要約すると、メッセージが送信される前に計算量の多い何かが発生した場合、そのメッセージは配信されず、そうでない場合です。

here および here と言われるように、スレッドからクライアントへのメッセージの送信はまったく問題ありません。

ここまでのすべての例で、サーバーはクライアントから送信されたイベントに応答します。ただし、一部のアプリケーションでは、サーバーがメッセージの発信元である必要があります。これは、バックグラウンドスレッドなど、サーバーで発生したイベントのクライアントに通知を送信するのに役立ちます。

これがサンプルコードです。コメントシャープ(#)を削除すると、メッセージ( 'foo from thread')はクライアントへの経路を見つけられません。

from flask import Flask
from flask.ext.socketio import SocketIO, emit
app = Flask(__name__)
socketio = SocketIO(app)

from threading import Thread
import time 

@socketio.on('client command')
def response(data):
    thread = Thread(target = testThreadFunction)
    thread.daemon = True
    thread.start()

    emit('client response', ['foo'])

def testThreadFunction():
#   time.sleep(1)

    socketio.emit('client response', ['foo from thread'])

socketio.run(app)

私はPython 3.4.3、Flask 0.10.1、フラスコ-socketio1.2、イベントレット0.17.4を使用しています。

このサンプルをコピーして.pyファイルに貼り付けると、動作を即座に再現できます。

誰かがこの奇妙な行動を説明できますか?

更新

イベントレットのバグのようです。私が行った場合:

socketio = SocketIO(app, async_mode='threading')

アプリケーションがインストールされていても、イベントレットを使用しないようにします。

ただし、async_modeがバイナリデータの受け入れを拒否するため、「スレッド化」を使用するため、これは私にとって適切な解決策ではありません。クライアントからサーバーにバイナリデータを送信するたびに、次のように表示されます。

WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.

3番目のオプションであるgeventをasync_modeとして使用することは私にとっては機能しません。また、geventはまだpython 3をサポートしていません。

他の提案はありますか?

22
Schnodderbalken

いくつかのPython関数にモンキーパッチを適用すると、Pythonがネイティブ関数の代わりにイベントレット関数を使用するようになります。このようにして、バックグラウンドスレッドがイベントレット。

https://github.com/miguelgrinberg/Flask-SocketIO/blob/e024b7ec9db4837196d8a46ad1cb82bc1e15f1f3/example/app.py#L30-L31

11
Schnodderbalken

私は同じ問題を抱えています。しかし、私は何が問題かわかったと思います。

次のコードでSocketIOを開始し、あなたのようなスレッドを作成すると、クライアントはサーバーが発行したメッセージを受信できません。

socketio = SocketIO(app) socketio.run()

Flask_socketioが document からstart_background_taskという名前の関数を提供していることがわかりました。

ここにそれの説明があります。

start_background_task(target、* args、** kwargs)

適切な非同期モデルを使用してバックグラウンドタスクを開始します。これは、アプリケーションが選択した非同期モードと互換性のあるメソッドを使用してバックグラウンドタスクを開始するために使用できるユーティリティ関数です。

パラメーター:

target –実行するターゲット関数。 args –関数に渡す引数。 kwargs –関数に渡すキーワード引数。この関数は、Python標準ライブラリのThreadクラスと互換性のあるオブジェクトを返します。

このオブジェクトのstart()メソッドは、この関数によってすでに呼び出されています。

そこで、コードthread=threading(target=xxx)socketio.start_background_task(target=xxx)に置き換え、次にsocketio.run()に置き換えます。サーバーは、実行時にスレッドでスタックします。つまり、関数start_background_taskは、スレッドが終了した後にのみ返されます。

次に、gunicornを使用してgunicorn --worker-class eventlet -w 1 web:app -b 127.0.0.1:5000でサーバーを実行しようとします

その後、すべてがうまくいきます!

したがって、スレッドを開始する適切な方法をstart_background_taskに選択させます。

3
殷明军