Branch Out
Many simulations need additional, concurrent activities in addition to their initial activities. However, concurrent programming is error-prone when actions are separated from each other. To handle this safely, μSim requires existing activities to branch out only temporarily.
Activities may branch out only after defining a scope of concurrency.
The most basic scope is opened by entering an async with Scope()
context.
In turn, a scope may do
several activities at once while it is active.
Lesson 02: Using a Concurrent Scope
In this example, we define an activity that uses a Scope
to concurrently run another activity several times.
Scopes are opened using ``async with Scope() as <name>:
,
followed by a block of actions which it covers. 1
We again use usim.time
to track and influence the progression of our simulation.
>>> from usim import time, Scope
>>>
>>> async def deliver_one(which):
... print('Delivering', which, 'at', time.now)
... await (time + 5)
... print('Delivered', which, 'at', time.now)
...
>>> async def deliver_all():
... print('-- Start deliveries at', time.now)
... async with Scope() as drivers: # 1
... drivers.do(deliver_one(1)) # 2
... drivers.do(deliver_one(2))
... await (time + 1) # 3
... drivers.do(deliver_one(3))
... print('Sent deliveries at', time.now) # 4.1
... print('-- Done deliveries at', time.now) # 4.2
Scopes can be difficult because they are inherently about doing several things at once. It helps to step through individual points of notice:
A scope must always be opened as an
async with
context - this allows us to suspend and resume branched off activities. You can freely choose a name; we recommend a name that reflects your simulation story.Activities are branched off using
scope.do(activity)
in place ofawait activity
. The current activity does not wait for the branched off activity to start or finish at this point.The current activity is free to perform other actions inside a scope. This includes calling functions and
await
ing notifications, or using loops/functions to create more activities to do.Transitioning out of a scope is delayed until all branched off activities have finished. 2 Statements directly before and after the end of scope do not happen in the same turn.
The primary purpose of scopes is to keep concurrent activities comprehensible.
>>> from usim import run
>>>
>>> run(deliver_all())
-- Start deliveries at 0
Delivering 1 at 0
Delivering 2 at 0
Sent deliveries at 1
Delivering 3 at 1
Delivered 1 at 5
Delivered 2 at 5
Delivered 3 at 6
-- Done deliveries at 6
Let’s take a step back…
So far, we have just run all activities to completion. Head over to the next section to cancel activities and notifications.