Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using threading.Timer with asycnio

I'm new to python's ascynio feature and I have a server that processes websocket requests from a browser. Here's a simplified version of how it works:

@asyncio.coroutine
def web_client_connected(self, websocket):
    self.web_client_socket = websocket

    while True:
        request = yield from self.web_client_socket.recv()
        json_val = process_request(request)
        yield from self.socket_queue.put(json_val)

@asyncio.coroutine
def push_from_web_client_json_queue(self):
    while True:
        json_val = yield from self.socket_queue.get()
        yield from self.web_client_socket.send(json_val)

You have one loop looking for web socket requests coming in from the client. When it gets one, it processes it and puts the value onto a queue. Another loop is looking for values on that queue and when it finds one it sends processed value back out on the web socket. Pretty straight forward and it works.

What I want to do now it introduce a timer. When requests comes and and is done processing, instead of putting a response back on the queue immediately, I want to start a timer for 1 minute. When the timer is finished, then I want to put the response on the queue.

I've tried something like:

@asyncio.coroutine
def web_client_connected(self, websocket):
    self.web_client_socket = websocket

    while True:
        request = yield from self.web_client_socket.recv()
        json_val = process_request(request)
        t = threading.Timer(60, self.timer_done, json_val)
        t.start()

@asyncio.coroutine
def timer_done(self, args):
    yield from self.socket_queue.put(args)

It doesn't work though. The timer_done method is never called. If I removed the @asyncio.coroutine decorator and yield from, then timer_done does get called but then call to self.socket_queue.put(args) doesn't work.

I think I'm misunderstanding something fundamental here. How do you do this?

like image 446
d512 Avatar asked Dec 30 '16 16:12

d512


1 Answers

Insted of a timer, use asyncio.ensure_future() and asyncio.sleep():

@asyncio.coroutine
def web_client_connected(self, websocket):
    self.web_client_socket = websocket

    while True:
        request = yield from self.web_client_socket.recv()
        json_val = process_request(request)
        asyncio.ensure_future(web_client_timer(json_val))
        yield

@asyncio.coroutine
def web_client_timer(self, json_val):
    yield from asyncio.sleep(60)
    yield from self.socket_queue.put(json_val)

Working example:

import asyncio


@asyncio.coroutine
def foo():
    print("enter foo")
    timers = []
    for i in range(10):
        print("Start foo", i)
        yield from asyncio.sleep(0.5)
        print("Got foo", i)
        timers.append(asyncio.ensure_future(timer(i)))
        yield
    print("foo waiting")
    # wait for all timers to finish
    yield from asyncio.wait(timers)
    print("exit foo")


@asyncio.coroutine
def timer(i):
    print("Setting timer", i)
    yield from asyncio.sleep(2)
    print("**** Timer", i)


loop = asyncio.get_event_loop()
resp = loop.run_until_complete(foo())
loop.close()
like image 84
Udi Avatar answered Oct 18 '22 00:10

Udi