Multi-threaded asyncio in Python - multithreading

I'm currently doing my first steps with asyncio in Python 3.5 and there is one problem that's bugging me. Obviously I haven't fully understood coroutines...
Here is a simplified version of what I'm doing.
In my class I have an open() method that creates a new thread. Within that thread I create a new event loop and a socket connection to some host. Then I let the loop run forever.
def open(self):
# create thread
self.thread = threading.Thread(target=self._thread)
self.thread.start()
# wait for connection
while self.protocol is None:
time.sleep(0.1)
def _thread(self):
# create loop, connection and run forever
self.loop = asyncio.new_event_loop()
coro = self.loop.create_connection(lambda: MyProtocol(self.loop),
'somehost.com', 1234)
self.loop.run_until_complete(coro)
self.loop.run_forever()
Stopping the connection is now quite simple, I just stop the loop from the main thread:
loop.call_soon_threadsafe(loop.stop)
Unfortunately I need to do some cleanup, especially I need to empty a queue before disconnecting from the server. So I tried something like this stop() method in MyProtocol:
class MyProtocol(asyncio.Protocol):
def __init__(self, loop):
self._loop = loop
self._queue = []
async def stop(self):
# wait for all queues to empty
while self._queue:
await asyncio.sleep(0.1)
# disconnect
self.close()
self._loop.stop()
The queue gets emptied from within the protocol's data_received() method, so I just want to wait for that to happen using the while loop with the asyncio.sleep() call. Afterwards I close the connection and stop the loop.
But how do I call this method from the main thread and wait for it?
I tried the following, but none of them seem to work (protocol is the currently used instance of MyProtocol):
loop.call_soon_threadsafe(protocol.stop)
loop.call_soon_threadsafe(functools.partial(asyncio.ensure_future, protocol.stop(), loop=loop))
asyncio.ensure_future(protocol.stop(), loop=loop)
Can anyone please help me here? Thanks!

Basically you want to schedule coroutine on loop of different thread. You could use run_coroutine_threadsafe:
future = asyncio.run_coroutine_threadsafe(protocol.stop, loop=loop)
future.result() # wait for results
Or the old style async like in https://stackoverflow.com/a/32084907/681044
import asyncio
from threading import Thread
loop = asyncio.new_event_loop()
def f(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
t = Thread(target=f, args=(loop,))
t.start()
#asyncio.coroutine
def g():
yield from asyncio.sleep(1)
print('Hello, world!')
loop.call_soon_threadsafe(asyncio.async, g())

Related

Run asyncio loop in a separate thread

I have a component of an application that needs to run an IOLoop in a separate thread. I try to achieve that by creating an new IOLOOP in a background Thread and starting the loop. My original use case it keep scheduling a bunch of async tasks periodically.
To achieve this, I:
Create an event loop in a background thread.
Start the thread and call asyncio.run_coroutine_threadsafe(self._start, self._loop)
import asyncio
from contextlib import suppress
from threading import Thread
class AsyncScheduler(object):
"""
Async Schedule Class.
This class:
- Will run on a separate event loop on a separate thread.
- Will periodically(every minute) schedule tasks for Requester.
"""
def __init__(self, batch_manager, requester):
self._requester = requester
self._is_started = False
self._tasks = []
self._loop = None
self.start()
def start(self):
"""
Start a new event loop in a thread.
call eventloop.run(self._start)
:return:
"""
print("STARTING")
self._loop = asyncio.new_event_loop()
# start new loop in thread.
Thread(target=self._loop.run_forever).start()
asyncio.run_coroutine_threadsafe(self._start, self._loop)
def stop(self):
if self._loop:
# cancel tasks
self._loop.call_soon_threadsafe(self._stop)
# stop the loop.
self._loop.stop()
async def _start(self):
"""
Create three tasks for 3 API versions.
Schedule Each tasks on the event loop using
asyncio.gather.
:return:
"""
versions = [1, 2, 3]
print("ASYNC START")
if not self._is_started:
self._is_started = True
for version in versions:
self._tasks.append(
self.create_task(60, version)
)
await asyncio.gather(*self._tasks)
async def _stop(self):
for task in self._tasks:
task.cancel()
with suppress(asyncio.CancelledError):
await task
async def execute(self, api_version):
"""
This method gets the batch to be executed and
tells the requester to run it.
:param api_version:
:return:
"""
await self._requester.run()
async def create_task(self, sleep_time, api_version):
"""
Calls the tasks in infinite loop.
:param sleep_time:
:param api_version:
:return:
"""
while True:
print("EVER CALLED")
await self.execute(api_version)
await asyncio.sleep(sleep_time)
Steps done in the code:
Call start from init
In start, create an eventloop within a new thread and start the loop with an awaitable.
I thought this is the way to use an event loop inside a separate thread. Alas, but my awaitbale sel._start is never called and I get an error [A coroutine object is required]
Any ideas, what am I messing up here?
Thanks & Regards & Happy Thanksgiving to folks who celebrate.

Python threading not running 2 processes at once

I have 2 functions that both have a while True loop in them and when I try running both at them at once, only the first one runs.
I have tried doing this with threading.Thread(target=hello()).start() as well as with multiprocessing.Process(target=hello()).start() and none worked.
import threading
def hello():
while True:
print("hello")
def world():
while True:
print("world")
threading.Thread(target=hello()).start()
threading.Thread(target=world()).start()
This happens because of CPython's Global Interpreter Lock . When you run your first thread, the second one is blocked and won't start running until the first is finished. In your case, the first thread has an infinite loop inside, so the second one is never awoken
What I'd suggest you is to use asyncio. It'll do the right work for you:
# python3.7+
import asyncio
async def hello():
while True:
print("Hello")
await asyncio.sleep(1)
async def world():
while True:
print("World")
await asyncio.sleep(1)
async def main():
await asyncio.gather(hello(), world())
asyncio.run(main())
Note asyncio.sleep(1) call in each coroutine. It's done to pass the control to the event loop and let another coroutine to be executed.

python Websockets (asyncio ver) force close connection

I am coding for python >3.5.
I am using Websockets 6.0 library that is here:
https://github.com/aaugustin/websockets
I have been call them the asyncio Websockets since they are based on asyncio.
In my search there were a lot of "lost connections", but I am looking at how to cancel a current ws.recv().
A call to the .start() creates a helper thread to start the asynico event loop. Then the receive function start and calls the connect function and websocket ws is instanced. Then the receive functions works fall messages. When I am ready to stop, a .stop() is called. I was expecting the stop funciton to stop the awaited ws.recv(). Then with the keep_running flag set to false and running a ws.close(), I would expect the ws.recv() to end and the when keep_running loop to end. That is not what is happening. I see all three stops, but never the receive stop.
command is: stop
Do command is stopped
Stop 1
Stop 2
Stop 3
^CException ignored in: <module 'threading' from '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py'>
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 1294, in _shutdown
t.join()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 1056, in join
self._wait_for_tstate_lock()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 1072, in _wait_for_tstate_lock
elif lock.acquire(block, timeout):
KeyboardInterrupt
(pyalmondplus) Pauls-MBP:pyalmondplus paulenright$
Code for reference:
import threading
import asyncio
import websockets
import json
class PyAlmondPlus:
def __init__(self, api_url, event_callback=None):
self.api_url = api_url
self.ws = None
self.loop = asyncio.get_event_loop()
self.receive_task = None
self.event_callback = event_callback
self.keep_running = False
async def connect(self):
print("connecting")
if self.ws is None:
print("opening socket")
self.ws = await websockets.connect(self.api_url)
print(self.ws)
async def disconnect(self):
pass
async def send(self, message):
pass
async def receive(self):
print("receive started")
while self.keep_running:
if self.ws is None:
await self.connect()
recv_data = await self.ws.recv()
print(recv_data)
print("receive ended")
def start(self):
self.keep_running = True
print("Start 1")
print("Start 2")
t = threading.Thread(target=self.start_loop, args=())
print("Start 3")
t.start()
print("Receiver running")
def start_loop(self):
print("Loop helper 1")
policy = asyncio.get_event_loop_policy()
policy.set_event_loop(policy.new_event_loop())
self.loop = asyncio.get_event_loop()
self.loop.set_debug(True)
asyncio.set_event_loop(self.loop)
self.loop.run_until_complete(self.receive())
print("Loop helper 2")
def stop(self):
print("Stop 1")
self.keep_running = False
print("Stop 2")
self.ws.close()
print("Stop 3")
I am looking at how to cancel a current ws.recv() [...] I see all three stops, but never the receive stop.
Your receive coroutine is likely suspended waiting for some data to arrive, so it's not in a position to check the keep_running flag.
The easy and robust way to stop a running coroutine is to cancel the asyncio Task that drives it. That will immediately un-suspend the coroutine and make whatever it was waiting for raise a CancelledError. When using cancel you don't need a keep_running flag at all, the exception will terminate the loop automatically.
A call to the .start() creates a helper thread to start the asynico event loop.
This works, but you don't really need a new thread and a whole new event loop for each instance of PyAlmondPlus. Asyncio is designed to run inside a single thread, so one event loop instance can host any number of coroutines.
Here is a possible design that implements both ideas (not tested with actual web sockets):
# pre-start a single thread that runs the asyncio event loop
bgloop = asyncio.new_event_loop()
_thread = threading.Thread(target=bgloop.run_forever)
_thread.daemon = True
_thread.start()
class PyAlmondPlus:
def __init__(self, api_url):
self.api_url = api_url
self.ws = None
async def connect(self):
if self.ws is None:
self.ws = await websockets.connect(self.api_url)
async def receive(self):
# keep_running is not needed - cancel the task instead
while True:
if self.ws is None:
await self.connect()
recv_data = await self.ws.recv()
async def init_receive_task(self):
self.receive_task = bgloop.create_task(self.receive())
def start(self):
# use run_coroutine_threadsafe to safely submit a coroutine
# to the event loop running in a different thread
init_done = asyncio.run_coroutine_threadsafe(
self.init_receive_task(), bgloop)
# wait for the init coroutine to actually finish
init_done.result()
def stop(self):
# Cancel the running task. Since the event loop is in a
# background thread, request cancellation with
# call_soon_threadsafe.
bgloop.call_soon_threadsafe(self.receive_task.cancel)

Calling coroutine and getting future in asyncio.Protocol.data_received()?

I need to get the future result inside asyncio loop, it is similar to Calling a coroutine from asyncio.Protocol.data_received
But asyncio in PY35 and PY34 are completely different, here are the code which could run correctly in PY34, but in PY35 it will pause at yield from and never return.
# PY34
class RelayClient(asyncio.Protocol):
pass
class Server(asyncio.Protocol):
def data_received(self, data):
# I need to connect to another address, and get future result at current function.
# Also I could not run loop.run_until_complete().
loop = asyncio.get_event_loop()
result = yield from loop.create_connection(RelayClient, 'www.google.com', 443)
do_some_thing_with_result(result)
So, how to do this in python 3.5?
Any advice is appreciated.
You cannot await a coroutine from a function that is not a coroutine. data_received is not a coroutine, so as was mentioned in the comments, you need to use the ensure_future helper to create a "background" task from your coroutine.
No need to start using callbacks however:
async def do_stuff(data):
result = await loop.create_connection(RelayClient, 'www.google.com', 443)
await do_some_thing_with_result(result)
class Server(asyncio.Protocol):
def data_received(self, data):
asyncio.ensure_future(do_stuff(data))
I would point out however, that asyncio gives no garanties whatsoever that data_received will be called with the complete data you are expecting. Usually the pattern you see in a Protocol looks a lot like this:
async def process_line(line):
...
class Server(asyncio.Protocol):
def __init__(self):
self.buffer = b''
def data_received(self, data):
self.buffer += data
if b'\n' not in self.buffer:
return
line, self.buffer = self.buffer.split(b'\n')
fut = asyncio.ensure_future(process_line(line))
fut.add_done_callback(self._handle_exception)
def _handle_exception(self, fut):
if fut.exception() is not None:
print('Processing failed', fut.exception())
(this is just an example, it copies the buffer way too much and would be very inefficient in most production use-cases)

Delay opening an asyncio connection

Some of my django REST services have to connect to an asyncio server to get some information. So I'm working in a threaded environment.
While connecting, the open_connection() takes an unreasonable 2 seconds (almost exactly, always just a bit more).
Client code:
import asyncio
import datetime
def call():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
#asyncio.coroutine
def msg_to_mars():
print("connecting", datetime.datetime.now())
reader, writer = yield from asyncio.open_connection('localhost', 8888, loop=loop)
print("connected", datetime.datetime.now()) # time reported here will be +2 seconds
return None
res = loop.run_until_complete(msg_to_mars())
loop.close()
return res
call()
Server code:
import asyncio
#asyncio.coroutine
def handle_connection(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
pass
loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
# Each client connection will create a new protocol instance
coro = asyncio.start_server(handle_connection, '0.0.0.0', 8888, loop=loop)
server = loop.run_until_complete(coro)
# Serve requests until Ctrl+C is pressed
print('MARS Device server serving on {}'.format(server.sockets[0].getsockname()))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
Both are basically copied from asyncio documentation samples for streamed communication, except for the additional assigning of event loop for threading.
How can I make this delay go away?
Turns out, the problem was in Windows DNS resolution.
Changing URLs from my computer name to 127.0.0.1 immediately killed the delays.

Resources