web-dev-qa-db-ja.com

Python-Autobahn | Python asynciowebsocketサーバーを別のサブプロセスまたはスレッドで実行している

Python 3.4.1で実行されているtkinterベースのGUIプログラムがあります。さまざまなURLからJSONデータを取得するためにプログラムで実行されているいくつかのスレッドがあります。いくつかのWebSocket機能を追加して、プログラムがサーバーとして機能し、複数のクライアントがWebSocketを介してプログラムに接続し、他のJSONデータを交換できるようにします。

Autobahn | PythonWebSocketサーバーをasyncioに使用しようとしています。

私は最初に、GUIプログラムの下の別のスレッドでasyncioイベントループを実行しようとしました。ただし、試行するたびに 'AssertionError:スレッド' Thread-1 'に現在のイベントループはありません。

次に、別のプロセスでasyncioイベントループを実行する標準ライブラリマルチプロセッシングパッケージを使用してプロセスを生成してみました。これを試してみると、例外は発生しませんが、WebSocketサーバーも起動しません。

別のPythonプログラムからのサブプロセスでasyncioイベントループを実行することさえ可能ですか?

Asyncioイベントループを現在マルチスレッド/ tkinterプログラムに統合する方法さえありますか?

[〜#〜] update [〜#〜]以下は、最初のテストで実行しようとしている実際のコードです。

from autobahn.asyncio.websocket import WebSocketServerProtocol
from autobahn.asyncio.websocket import WebSocketServerFactory
import asyncio
from multiprocessing import Process

class MyServerProtocol(WebSocketServerProtocol):

   def onConnect(self, request):
      print("Client connecting: {0}".format(request.peer))

   def onOpen(self):
      print("WebSocket connection open.")

   def onMessage(self, payload, isBinary):
      if isBinary:
         print("Binary message received: {0} bytes".format(len(payload)))

      else:
         print("Text message received: {0}".format(payload.decode('utf8')))

      ## echo back message verbatim
      self.sendMessage(payload, isBinary)

   def onClose(self, wasClean, code, reason):
      print("WebSocket connection closed: {0}".format(reason))

def start_server():
   factory = WebSocketServerFactory("ws://10.241.142.27:6900", debug = False)
   factory.protocol = MyServerProtocol
   loop = asyncio.get_event_loop()
   coro = loop.create_server(factory, '10.241.142.27', 6900)
   server = loop.run_until_complete(coro)
   loop.run_forever()
   server.close()
   loop.close()


websocket_server_process = Process(target = start_server)
websocket_server_process.start()

そのほとんどは、asyncioのAutobahn | Pythonサンプルコードから直接引用したものです。プロセスとして実行しようとすると、何も実行されず、クライアントは接続できません。netstat-aを実行すると、ポート6900は使用されません。メインプログラムでstart_server()を使用するだけで、WebSocketサーバーが作成されます。

21
user2662241

まず、asyncioではプログラム内の各スレッドに独自のイベントループが必要であるため、_AssertionError: There is no current event loop in thread 'Thread-1'._を取得していますが、メインスレッドでイベントループが自動的に作成されるだけです。したがって、メインスレッドで_asyncio.get_event_loop_を1回呼び出すと、ループオブジェクトが自動的に作成され、デフォルトとして設定されますが、子スレッドで再度呼び出すと、そのエラーが発生します。代わりに、スレッドの開始時にイベントループを明示的に作成/設定する必要があります。

_loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
_

これを実行すると、その特定のスレッドでget_event_loop()を使用できるようになります。

asyncioを介して開始されたサブプロセスでmultiprocessingイベントループを開始することができます。

_import asyncio
from multiprocessing import Process 

@asyncio.coroutine
def coro():
    print("hi")

def worker():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(coro())

if __name__ == "__main__":
    p = Process(target=worker)
    p.start()
    p.join()
_

出力:

_hi
_

唯一の注意点は、子だけでなく親プロセスでもイベントループを開始する場合、Unixプラットフォームを使用している場合は、子で新しいイベントループを明示的に作成/設定する必要があることです( Pythonのバグ )。 Windowsで、または 'spawn' multiprocessingコンテキストを使用する場合は正常に機能するはずです。

Tkinterアプリケーションのバックグラウンドスレッド(またはプロセス)でasyncioイベントループを開始し、tkinterasyncioの両方のイベントループを実行することが可能であると思います。 -並べて。バックグラウンドスレッド/プロセスからGUIを更新しようとした場合にのみ、問題が発生します。

26
dano

@ dano による答えは正しいかもしれませんが、ほとんどの状況で不要な新しいプロセスを作成します。

私自身も同じ問題を抱えていたので、この質問をGoogleで見つけました。 WebSocket APIをメインスレッドで実行しないようにしたいアプリケーションを作成しましたが、これが問題の原因でした。

pythonドキュメントでイベントループについて読むだけで別の解決策を見つけ、この問題を解決するasyncio.new_event_loop関数とasyncio.set_event_loop関数を見つけました。

私はAutoBahnを使用しませんでしたが、pypiwebsocketsライブラリを使用しました。これが私の解決策です

import websockets
import asyncio
import threading

class WebSocket(threading.Thread):    
    @asyncio.coroutine
    def handler(self, websocket, path):
        name = yield from websocket.recv()
        print("< {}".format(name))
        greeting = "Hello {}!".format(name)
        yield from websocket.send(greeting)
        print("> {}".format(greeting))

    def run(self):
        start_server = websockets.serve(self.handler, '127.0.0.1', 9091)
        eventloop = asyncio.new_event_loop()
        asyncio.set_event_loop(eventloop)
        eventloop.run_until_complete(start_server)
        eventloop.run_forever()

if __name__ == "__main__":
    ws = WebSocket()
    ws.start()
4

「asyncioイベントループを現在マルチスレッド/ tkinterプログラムに統合する方法さえありますか?」

はい、asyncioイベントループを使用してtkinterプログラムを実行します。コンセプトの証明。

'''Proof of concept integrating asyncio and tk loops.

Terry Jan Reedy
Run with 'python -i' or from IDLE editor to keep tk window alive.
'''

import asyncio
import datetime as dt
import tkinter as tk

loop = asyncio.get_event_loop()
root = tk.Tk()

# Combine 2 event loop examples from BaseEventLoop doc.
# Add button to prove that gui remain responsive between time updates.
# Prints statements are only for testing.

def flipbg(widget, color):
    bg = widget['bg']
    print('click', bg, loop.time())
    widget['bg'] = color if bg == 'white' else 'white'

hello = tk.Label(root)
flipper = tk.Button(root, text='Change hello background', bg='yellow',
                    command=lambda: flipbg(hello, 'red'))
time = tk.Label(root)
hello.pack()
flipper.pack()
time.pack()

def hello_world(loop):
    hello['text'] = 'Hello World'
loop.call_soon(hello_world, loop)

def display_date(end_time, loop):
    print(dt.datetime.now())
    time['text'] = dt.datetime.now()
    if (loop.time() + 1.0) < end_time:
        loop.call_later(1, display_date, end_time, loop)
    else:
        loop.stop()

end_time = loop.time() + 10.1
loop.call_soon(display_date, end_time, loop)

# Replace root.mainloop with these 4 lines.
def tk_update():
    root.update()
    loop.call_soon(tk_update)  # or loop.call_later(delay, tk_update)
# Initialize loop before each run_forever or run_until_complete call    
tk_update() 
loop.run_forever()

私はこれらの4行を追加してIDLEを実験的に実行しましたが、構文が数千行を強調表示している場合にのみ速度低下が顕著になります。

2
Terry Jan Reedy