Ath.Bar.
Ath.Bar.

Reputation: 306

In python, how can I run a function without the program waiting for its completion?

In Python, I am making a cube game (like Minecraft pre-classic) that renders chunk by chunk (16x16 blocks). It only renders blocks that are not exposed (not covered on all sides). Even though this method is fast when I have little height (like 16x16x2, which is 512 blocks in total), once I make the terrain higher (like 16x16x64, which is 16384 blocks in total), rendering each chunk takes roughly 0.03 seconds, meaning that when I render multiple chunks at once the game freezes for about a quarter of a second. I want to render the chunks "asynchronously", meaning that the program will keep on drawing frames and calling the chunk render function multiple times, no matter how long it takes. Let me show you some pictures to help:

Image describing that if I walk far enough, chunks will be rendered on all sides

I tried to make another program in order to test it:

import threading

def run():
    n=1
    for i in range(10000000):
       n += 1
    print(n)

print("Start")
threading.Thread(target=run()).start()
print("End")

I know that creating such a lot of threads is not the best solution, but nothing else worked.

Threading, however, didn't work, as this is what the output looked like:

>>> Start
>>> 10000001
>>> End

It also took about a quarter of a second to complete, which is about how long the multiple chunk rendering takes.

Then I tried to use async:

import asyncio

async def run():
    n = 1
    for i in range(10000000):
       n += 1
    print(n)

print("Start")
asyncio.run(run())
print("End")

It did the exact same thing.

My questions are: Can I run a function without stopping/pausing the program execution until it's complete? Did I use the above correctly?

Upvotes: 0

Views: 1829

Answers (2)

Kryštof Vosyka
Kryštof Vosyka

Reputation: 585

Yes. No. The answer is complicated.

First, your example has at least one error on it:

print("Start")
threading.Thread(target=run).start() #notice the missing parenthesis after run
print("End")

You can use multithreading for your game of course, but it can come at a disadvantage of code complexity because of synchronization and you might not gain any performance because of GIL.

asyncio is probably not for this job either, since you don't need to highly parallelize many tasks and it has the same problems with GIL as multithreading.

The usual solution for this kind of problem is to divide your work into small batches and only process the next batch if you have time to do so on the same frame, kind of like so:

def runBatch(range):
  for x in range:
    print(x)

batches = [range (x, x+200) for x in range(0, 10000, 200)]
while (true): # main loop
  while (timeToNextFrame() > 15): 
    runBatch(batch.pop())
  renderFrame() #or whatever

However, in this instance, optimizing the algorithm itself could be even better than any other option. One thing that Minecraft does is it subdivides chunks into subchunks (you can mostly ignore subchunks that are full of blocks). Another is that it only considers the visible surfaces of the blocks (renders only those sides of the block that could be visible, not the whole block).

Upvotes: 2

Phani Kumar
Phani Kumar

Reputation: 181

asyncio only works asynchronously only when your function is waiting on I/O task like network call or wait on disk I/O etc.

for non I/O tasks to execute asynchronously multi-threading is the only option so create all your threads and wait for the threads to complete their tasks using thread join method

from threading import Thread
import time

def draw_pixels(arg):
    time.sleep(arg)
    print(arg)

threads = []
args = [1,2,3,4,5]
for arg in args:
    t = Thread(target=draw_pixels, args=(arg, ))
    t.start()
    threads.append(t)

# join all threads
for t in threads:
    t.join()

Upvotes: 0

Related Questions