@@ -11,6 +11,11 @@ It is not an introduction into ISR coding. For this see
11
11
and [ this doc] ( https://github.com/peterhinch/micropython-async/blob/master/v3/docs/INTERRUPTS.md )
12
12
which provides specific guidance on interfacing ` uasyncio ` with ISR's.
13
13
14
+ Because of [ this issue] ( https://github.com/micropython/micropython/issues/7965 )
15
+ the ` ThreadSafeFlag ` class does not work under the Unix build. The classes
16
+ presented here depend on this: none can be expected to work on Unix until this
17
+ is fixed.
18
+
14
19
# Contents
15
20
16
21
1 . [ Introduction] ( ./THREADING.md#1-introduction ) The various types of pre-emptive code.
@@ -25,6 +30,8 @@ which provides specific guidance on interfacing `uasyncio` with ISR's.
25
30
  ;  ;  ;  ;  ; 2.2.3 [ Object ownership] ( ./THREADING.md#223-object-ownership )
26
31
3 . [ Synchronisation] ( ./THREADING.md#3-synchronisation )
27
32
3.1 [ Threadsafe Event] ( ./THREADING.md#31-threadsafe-event )
33
+ 3.2 [ Message] ( ./THREADING.md#32-message ) A threadsafe event with data payload.
34
+ 4 . [ Taming blocking functions] ( ./THREADING.md#4-taming-blocking-functions )
28
35
29
36
# 1. Introduction
30
37
@@ -376,3 +383,165 @@ async def main():
376
383
377
384
asyncio.run(main())
378
385
```
386
+ ## 3.2 Message
387
+
388
+ The ` Message ` class uses [ ThreadSafeFlag] ( ./TUTORIAL.md#36-threadsafeflag ) to
389
+ provide an object similar to ` Event ` with the following differences:
390
+
391
+ * ` .set() ` has an optional data payload.
392
+ * ` .set() ` can be called from another thread, another core, or from an ISR.
393
+ * It is an awaitable class.
394
+ * Payloads may be retrieved in an asynchronous iterator.
395
+ * Multiple tasks can wait on a single ` Message ` instance.
396
+
397
+ Constructor:
398
+ * No args.
399
+
400
+ Synchronous methods:
401
+ * ` set(data=None) ` Trigger the ` Message ` with optional payload (may be any
402
+ Python object).
403
+ * ` is_set() ` Returns ` True ` if the ` Message ` is set, ` False ` if ` .clear() ` has
404
+ been issued.
405
+ * ` clear() ` Clears the triggered status. At least one task waiting on the
406
+ message should issue ` clear() ` .
407
+ * ` value() ` Return the payload.
408
+
409
+ Asynchronous Method:
410
+ * ` wait() ` Pause until message is triggered. You can also ` await ` the message
411
+ as per the examples.
412
+
413
+ The ` .set() ` method can accept an optional data value of any type. The task
414
+ waiting on the ` Message ` can retrieve it by means of ` .value() ` or by awaiting
415
+ the ` Message ` as below. A ` Message ` can provide a means of communication from
416
+ an interrupt handler and a task. The handler services the hardware and issues
417
+ ` .set() ` which causes the waiting task to resume (in relatively slow time).
418
+
419
+ This illustrates basic usage:
420
+ ``` python
421
+ import uasyncio as asyncio
422
+ from threadsafe import Message
423
+
424
+ async def waiter (msg ):
425
+ print (' Waiting for message' )
426
+ res = await msg
427
+ print (' waiter got' , res)
428
+ msg.clear()
429
+
430
+ async def main ():
431
+ msg = Message()
432
+ asyncio.create_task(waiter(msg))
433
+ await asyncio.sleep(1 )
434
+ msg.set(' Hello' ) # Optional arg
435
+ await asyncio.sleep(1 )
436
+
437
+ asyncio.run(main())
438
+ ```
439
+ The following example shows multiple tasks awaiting a ` Message ` .
440
+ ``` python
441
+ from threadsafe import Message
442
+ import uasyncio as asyncio
443
+
444
+ async def bar (msg , n ):
445
+ while True :
446
+ res = await msg
447
+ msg.clear()
448
+ print (n, res)
449
+ # Pause until other coros waiting on msg have run and before again
450
+ # awaiting a message.
451
+ await asyncio.sleep_ms(0 )
452
+
453
+ async def main ():
454
+ msg = Message()
455
+ for n in range (5 ):
456
+ asyncio.create_task(bar(msg, n))
457
+ k = 0
458
+ while True :
459
+ k += 1
460
+ await asyncio.sleep_ms(1000 )
461
+ msg.set(' Hello {} ' .format(k))
462
+
463
+ asyncio.run(main())
464
+ ```
465
+ Receiving messages in an asynchronous iterator:
466
+ ``` python
467
+ import uasyncio as asyncio
468
+ from threadsafe import Message
469
+
470
+ async def waiter (msg ):
471
+ async for text in msg:
472
+ print (f " Waiter got { text} " )
473
+ msg.clear()
474
+
475
+ async def main ():
476
+ msg = Message()
477
+ task = asyncio.create_task(waiter(msg))
478
+ for text in (" Hello" , " This is a" , " message" , " goodbye" ):
479
+ msg.set(text)
480
+ await asyncio.sleep(1 )
481
+ task.cancel()
482
+ await asyncio.sleep(1 )
483
+ print (" Done" )
484
+
485
+ asyncio.run(main())
486
+ ```
487
+ The ` Message ` class does not have a queue: if the instance is set, then set
488
+ again before it is accessed, the first data item will be lost.
489
+
490
+ # 4. Taming blocking functions
491
+
492
+ Blocking functions or methods have the potential of stalling the ` uasyncio `
493
+ scheduler. Short of rewriting them to work properly the only way to tame them
494
+ is to run them in another thread. The following is a way to achieve this.
495
+ ``` python
496
+ async def unblock (func , * args , ** kwargs ):
497
+ def wrap (func , message , args , kwargs ):
498
+ message.set(func(* args, ** kwargs)) # Run the blocking function.
499
+ msg = Message()
500
+ _thread.start_new_thread(wrap, (func, msg, args, kwargs))
501
+ return await msg
502
+ ```
503
+ Given a blocking function ` blocking ` taking two positional and two keyword args
504
+ it may be awaited in a ` uasyncio ` task with
505
+ ``` python
506
+ res = await unblock(blocking, 1 , 2 , c = 3 , d = 4 )
507
+ ```
508
+ The function runs "in the background" with other tasks running; only the
509
+ calling task is paused. Note how the args are passed. There is a "gotcha" which
510
+ is cancellation. It is not valid to cancel the ` unblock ` task because the
511
+ underlying thread will still be running. There is no general solution to this.
512
+ If the specific blocking function has a means of interrupting it or of forcing
513
+ a timeout then it may be possible to code a solution.
514
+
515
+ The following is a complete example where blocking is demonstrated with
516
+ ` time.sleep ` .
517
+ ``` python
518
+ import uasyncio as asyncio
519
+ from threadsafe import Message
520
+ import _thread
521
+ from time import sleep
522
+
523
+ def slow_add (a , b , * , c , d ): # Blocking function.
524
+ sleep(5 )
525
+ return a + b + c + d
526
+
527
+ # Convert a blocking function to a nonblocking one using threading.
528
+ async def unblock (func , * args , ** kwargs ):
529
+ def wrap (func , message , args , kwargs ):
530
+ message.set(func(* args, ** kwargs)) # Run the blocking function.
531
+ msg = Message()
532
+ _thread.start_new_thread(wrap, (func, msg, args, kwargs))
533
+ return await msg
534
+
535
+ async def busywork (): # Prove uasyncio is running.
536
+ while True :
537
+ print (" #" , end = " " )
538
+ await asyncio.sleep_ms(200 )
539
+
540
+ async def main ():
541
+ bw = asyncio.create_task(busywork())
542
+ res = await unblock(slow_add, 1 , 2 , c = 3 , d = 4 )
543
+ bw.cancel()
544
+ print (f " \n Done. Result = { res} " )
545
+
546
+ asyncio.run(main())
547
+ ```
0 commit comments