Michał Szostak
Michał Szostak

Reputation: 23

How to run a periodic task and a while loop asynchronously

I have two functions:

i = 0

def update():
    global i
    i += 1

def output():
    print(i)

I want to run output() every 3 seconds and loop update() without any interval, both of course asynchronously.

I tried using asyncio, threading, multithreading and timeloop but I couldn't get it to work in neither of these libraries. If you figure out how to do it in any of these libraries, or some other library, please help. I'm ok with working with any library.

Upvotes: 0

Views: 998

Answers (1)

Louis Lac
Louis Lac

Reputation: 6436

Using AsyncIO this would resemble:

import asyncio


def update(value):
    value["int"] += 1

def output(value):
    print(value["int"])

async def update_worker(value):
    while True:
        update(value)
        await asyncio.sleep(0)

async def output_worker(value):
    while True:
        output(value)
        await asyncio.sleep(3)

async def main():
    value = {"int": 0}

    await asyncio.gather(
        update_worker(value), 
        output_worker(value))


if __name__ == "__main__":
    asyncio.run(main())

Notice that I changed the global value to be a shared value since it is best practice to do so. In other programming languages it would be unsafe to share a value both in read and write to multiple concurrent contexts but since most Python objects are thread safe is is ok in this case. Otherwise, you should use a mutex of any other concurrency primitive to synchronise reads and writes.

AsyncIO concurrency is based on a cooperative multitasking model so asynchronous tasks must explicitly yield control to other concurrent tasks when they are waiting for something (noted by all await keywords). Thus, to ensure that output_worker has a chance to run one must add an await asyncio.sleep(0) in the infinite loop of the update_worker so that the AsyncIO event loop can run output_worker.

Here is the same code using multithreading instead of AsyncIO:

from time import sleep
from threading import Thread, Lock

def update(value, lock):
    with lock:
        value["int"] += 1

def output(value, lock):
    with lock:
        print(value["int"])

def update_worker(value, lock):
    while True:
        update(value, lock)

def output_worker(value, lock):
    while True:
        output(value, lock)
        sleep(3)

def main():
    value = {"int": 0}
    lock = Lock()

    t1 = Thread(target=update_worker, args=(value, lock), daemon=True)
    t2 = Thread(target=output_worker, args=(value, lock), daemon=True)

    t1.start()
    t2.start()

    t1.join()
    t2.join()

if __name__ == "__main__":
    main()

Even though it is not necessary in this particular Python program, I used a Lock to synchronize reads and writes as it is the correct way to handle concurrency.

Upvotes: 1

Related Questions