diff --git a/README.md b/README.md index 54a800f..10484bc 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,53 @@ other specific exception, in order to avoid catching by mistake `CancelledError` (object of `Exception`, used internally to propagate cancellation). +### Yielding + +If you need to give control back to the event loop, but there's no future you +need to `await` at that point, you can call `yieldAsync()` - a simple wrapper +around `sleepAsync()`: + +```nim +template yieldAsync*() = + await sleepAsync(0.seconds) +``` + +This comes in handy when splitting a blocking task into smaller parts: + +```nim +proc showMsg(after: int) {.async.} = + echo "begin showMsg()" + await sleepAsync(after.seconds) + echo "msg: after = ", after + +let maxDuration = 10 + +proc heavy() {.async.} = + echo "begin showMsg(", after, ")" + let start = Moment.now() + var s: uint64 + while true: + s += 1 + + # try commenting this out and see what happens + if s mod 1000000 == 0: + yieldAsync() + + if (Moment.now() - start).seconds >= (maxDuration + 1): + break + echo "sum: ", s + +proc p1() {.async.} = + var futures: seq[Future[void]] + + for after in 1..maxDuration: + futures.add(showMsg(after)) + futures.add(heavy()) + await allFutures(futures) + +waitFor p1() +``` + ## TODO * Pipe/Subprocess Transports. * Multithreading Stream/Datagram servers diff --git a/chronos/asyncloop.nim b/chronos/asyncloop.nim index 405c302..b97a9d7 100644 --- a/chronos/asyncloop.nim +++ b/chronos/asyncloop.nim @@ -891,6 +891,10 @@ proc sleepAsync*(ms: int): Future[void] {. inline, deprecated: "Use sleepAsync(Duration)".} = result = sleepAsync(ms.milliseconds()) +# This can't be an async proc, because its `await` needs to modify the caller. +template yieldAsync*() = + await sleepAsync(0.seconds) + proc withTimeout*[T](fut: Future[T], timeout: Duration): Future[bool] = ## Returns a future which will complete once ``fut`` completes or after ## ``timeout`` milliseconds has elapsed. diff --git a/tests/testfut.nim b/tests/testfut.nim index d0e4bc6..73a1cd5 100644 --- a/tests/testfut.nim +++ b/tests/testfut.nim @@ -969,6 +969,38 @@ suite "Future[T] behavior test suite": proc testCancellationRace(): bool = waitFor(testCancellationRaceAsync()) + proc testYieldAsync(): Future[bool] {.async.} = + proc showMsg(after: int, results: ptr seq[string]) {.async.} = + await sleepAsync(after.seconds) + results[].add("after " & $after) + + let maxDuration = 2 + + proc heavy(results: ptr seq[string]) {.async.} = + let start = Moment.now() + var s: uint64 + while true: + s += 1 + if s mod 1000000 == 0: + yieldAsync() + if (Moment.now() - start).seconds >= (maxDuration + 1): + break + results[].add("heavy") + + var + futures: seq[Future[void]] + results: seq[string] + + for after in 1..maxDuration: + futures.add(showMsg(after, results.addr)) + futures.add(heavy(results.addr)) + await allFutures(futures) + + return results == @["after 1", "after 2", "heavy"] + + proc testYield(): bool = + waitFor(testYieldAsync()) + test "Async undefined behavior (#7758) test": check test1() == true test "Immediately completed asynchronous procedure test": @@ -1022,3 +1054,6 @@ suite "Future[T] behavior test suite": check testWithTimeout() == true test "Cancellation race test": check testCancellationRace() == true + test "Yielding test": + check testYield() == true +