Asynchronous prime-checking

Moving on from our starting counting-down example, let's reconsider the example from the previous chapter. As a refresher, the following is the code for the synchronous version of the program:

# Chapter09/example1.py

from math import sqrt

def is_prime(x):
print('Processing %i...' % x)

if x < 2:
print('%i is not a prime number.' % x)

elif x == 2:
print('%i is a prime number.' % x)

elif x % 2 == 0:
print('%i is not a prime number.' % x)

else:
limit = int(sqrt(x)) + 1
for i in range(3, limit, 2):
if x % i == 0:
print('%i is not a prime number.' % x)
return

print('%i is a prime number.' % x)

if __name__ == '__main__':

is_prime(9637529763296797)
is_prime(427920331)
is_prime(157)

As we discussed in the last chapter, here, we have a simple prime-checking function, is_prime(x), that prints out messages indicating whether the input integer that it takes in, x, is a prime number. In our main program, we call is_prime() on three prime numbers, in an order of decreasing magnitude sequentially. This setup again creates a significant period of time during which the program appears to be hanging while processing the large input, resulting in a low responsiveness for the program.

The output produced by the program will look similar to the following:

Processing 9637529763296797...
9637529763296797 is a prime number.
Processing 427920331...
427920331 is a prime number.
Processing 157...
157 is a prime number.

To implement asynchronous programming for this script, first, we will have to create our first main component: the event loop. To do this, instead of using the '__main__' scope, we will convert it to a separate function. This function and our is_prime() prime-checking function will be the coroutines in our final asynchronous program.

Now, we need to convert both the is_prime() and main() functions into coroutines; again, this means putting the async keyword in front of the def keyword, and the await keyword inside each function, to specify the task-switching event. For main(), we simply implement that event while waiting for the task queue by using aysncio.wait(), as follows:

# Chapter09/example2.py

async def main():

task1 = loop.create_task(is_prime(9637529763296797))
task2 = loop.create_task(is_prime(427920331))
task3 = loop.create_task(is_prime(157))

await asyncio.wait([task1, task2, task3])

Things are more complicated in the is_prime() function, as there is no clear point during which the execution flow should be released back to the event loop, like in our previous counting-down example. Recall that the goal of asynchronous programming is to achieve a better execution time and responsiveness, and to implement this, the task-switching event should take place during a heavy, long-running task. This requirement, however, is dependent on the specifics of your program—particularly, the coroutine, the task queue of the program, and the individual tasks in the queue.

For example, the task queue of our program consists of three numbers: 9637529763296797, 427920331, and 157; in order, we can consider them as a large task, a medium task, and a small task. To improve responsiveness, we would like to switch tasks during the large task, and not during the small task. This setup will allow the medium and small tasks to be started, processed, and maybe finished during the execution of the large task, even if the large task is in front in the task queue of the program.

Then, we will consider our is_prime() coroutine. After checking for some specific edge cases, it iterates in a for loop through every odd number under the square root of the input integer and tests for the divisibility of the input with regards to the current odd number in question. Inside this long-running for loop, then, is the perfect place to switch tasks—that is, to release the execution flow back to the event loop.

However, we still need to decide at which specific points in the for loop to implement the task-switching event. Again, taking into account the individual tasks in the task queue, we are looking for a point that is fairly common in the large task, not so common in the medium task, and non-existent in the small task. I have decided that this point is every 1,00,000-number period, which does satisfy our requirements, and I have used the await asyncio.sleep(0) command to facilitate the task-switching event, as follows:

# Chapter09/example2.py

from math import sqrt
import asyncio

async def is_prime(x):
print('Processing %i...' % x)

if x < 2:
print('%i is not a prime number.' % x)

elif x == 2:
print('%i is a prime number.' % x)

elif x % 2 == 0:
print('%i is not a prime number.' % x)

else:
limit = int(sqrt(x)) + 1
for i in range(3, limit, 2):
if x % i == 0:
print('%i is not a prime number.' % x)
return
elif i % 100000 == 1:
await asyncio.sleep(0)

print('%i is a prime number.' % x)

Finally, in our main program (not to be confused with the main() coroutine), we create our event loop and use it to run our main() coroutine, until it completes its execution:

try:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
except Exception as e:
print('There was a problem:')
print(str(e))
finally:
loop.close()

As you saw in the previous chapter, better responsiveness was achieved through this asynchronous version of the script. Specifically, instead of appearing like it is hanging while processing the first large task, our program now prints out output messages for the other, smaller tasks, before it finishes executing the large task. Our end result will look similar to the following:

Processing 9637529763296797...
Processing 427920331...
427920331 is a prime number.
Processing 157...
157 is a prime number.
9637529763296797 is a prime number.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.205.27