This is my try:
proc doWork(fut: Future[void]) =
# staff use sql
sleep(5000)
# complete the future var
fut.complete()
proc callWorkAsync() {.async.} =
let fut = newFuture[void]()
spawn doWork(fut)
await fut
Because the paramater is deeply copied, the code doesn't work, the callWorkAysnc never return. When I use the global Future variable, the compiler refuse to compile with the error " 'doWork' is not GC-safe as it accesses 'gFut' which is a global using GC'ed memory"
Hope someone would help me to solve the problem, thanks.
Have you tried this? asyncpg
To wait for some result of Spawn's task, you can confirm by using isReady() function.
For example, ChatApp#L38
Find a way to solve the problem. Here is the code:
proc doWork(evt: AsyncEvent) {.gcsafe.} =
sleep(3000)
evt.trigger()
echo "work over"
proc waitEvent(ev: AsyncEvent): Future[void] =
var fut = newFuture[void]("waitEvent")
proc cb(fd: AsyncFD): bool = fut.complete(); return true
addEvent(ev, cb)
return fut
proc callAsync() {.async.} =
let evt = newAsyncEvent()
let fut = waitEvent(evt)
spawn doWork(evt)
await fut
waitFor callAsync()
echo "future return"
you can also busy poll the result of the spawned proc in an async proc like that:
import os, asyncdispatch, threadpool
proc doWork(): bool =
## we need a return type
# staff use sql
echo "starting..."
sleep(5000)
# complete the future var
echo "... ok"
return true
proc asyncWaitFor[T] (work: FlowVar[T], sleepTime: int = 500):Future[T] {.async.} =
## waits asynchron for a FlowVar to finish
while not work.isReady :
await sleepAsync(sleepTime)
return ^work
proc callWorkAsync() {.async.} =
let work = spawn doWork()
discard await asyncWaitFor(work)
let call = callWorkAsync()
echo "do some more"
waitFor call
echo "done"
Now there is another problem. I hope I can write the code like this:
proc ff1(p1: int): Result =
# my db acess code here
......
proc ff2(p1, p2: int): Result =
# my db acess code here
......
proc main() {.async.} =
# async call ff1 with parameter
let r1 = await callAsync(ff1(1))
# async call ff2 with parameter
let r2 = await callAsync(ff2(1, 2))
......
Because we pass the parameter 'ff1(1)' to callAsync, so callAsync must be a macro like this:
macro callAsync*(p: untyped): untyped {.async.} =
quote do:
proc callme(evt: AsyncEvent): Result =
let s = `p`
evt.trigger()
s
let evt = newAsyncEvent()
let fut = waitEvent(evt)
let val = spawn callme(evt)
await fut
return ^val
But a macro cannot be {.async.}, so I cannot do this.
A limited way to resolve this problem is use an procval, here is the code:
type
Result* = ref object of RootObj
body*: string
Action = proc(): Result
proc waitEvent(evt: AsyncEvent): Future[void] =
let fut = newFuture[void]("callAsync")
addEvent(evt, (fd: AsyncFD) => (fut.complete(); true))
return fut
proc callp(evt: AsyncEvent, p: Action): Result =
let v = p()
evt.trigger()
v
proc callAsync*(p: Action): Future[Result] {.async.} =
let evt = newAsyncEvent()
let fut = waitEvent(evt)
let val = spawn callp(evt, p)
await fut
return ^val
proc ff(): Result =
echo "work"
sleep(1000)
Result(body: "hello")
block:
let val = waitFor callAsync(ff)
echo "future return ", val.body
The limitation is I can only call Action type proc, and I cannot pass the paramter freely.
Any way to solve the problem? Thanks.
I agree that would be fine. The two are very similar. To avoid the polling, threadpool should be able to transfer the result of its work to the async dispatch list of the calling thread when its done.
expanding my example you could do:
import os, asyncdispatch, threadpool
type Noise = distinct bool
proc doWork(param: int): Noise =
# staff use sql
echo "starting with ", param, " ..."
sleep(5000)
# complete the future var
echo "... ok"
proc doOtherWork(p1:int, p2: int): Noise =
echo "other starting with ", p1, " and ", p2, " ..."
sleep(4000)
# complete the future var
echo "... other ok"
proc doRealWork(p1, p2, p3: int): int =
echo "real starting with ", p1, " and ", p2, " and ", p3, " ..."
sleep(3000)
result = p1 + p2 + p3
echo "... done real work"
proc asyncWaitFor (work: FlowVar[Noise], sleepTime: int = 500) {.async.} =
## waits asynchron for a FlowVar to finish
while not work.isReady :
await sleepAsync(sleepTime)
proc asyncWaitFor[T: not Noise] (work: FlowVar[T], sleepTime: int = 500):Future[T] {.async.} =
## waits asynchron for a FlowVar to finish
while not work.isReady :
await sleepAsync(sleepTime)
return ^work
proc callWorkAsync() {.async.} =
let work = spawn doWork(1)
let other = spawn doOtherWork(2, 3)
let real = spawn doRealWork(4, 5, 6)
let res = await asyncWaitFor(real)
echo "real work returned: ", res
await asyncWaitFor(work)
await asyncWaitFor(other)
let call = callWorkAsync()
echo "do so more"
waitFor call
echo "done"
It works finally. :) To make the {.async.} works in quoted code, the 'await' and Future variable must be ident!!
type
Result* = ref object of RootObj
body*: string
proc waitEvent(evt: AsyncEvent): Future[void] =
var fut = newFuture[void]("callAsync")
addEvent(evt, (fd: AsyncFD) => (fut.complete(); true))
return fut
macro callAsync(p: untyped): untyped =
let await = ident("await")
let fut = ident("fut")
quote do:
block:
proc callp(evt: AsyncEvent): Result =
let v = `p`
evt.trigger()
v
proc async_call(): Future[Result] {.async.} =
let evt = newAsyncEvent()
let `fut` = waitEvent(evt)
let val = spawn callp(evt)
`await` `fut`
return ^val
async_call()
proc ff1(v: int): Result =
echo "work ", v
sleep(1000)
Result(body: "hello " & $v)
proc ff2(v1, v2: int): Result =
echo "work ", v1, " ", v2
sleep(1000)
Result(body: "hello " & $v1 & " " & $v2)
when isMainModule:
block:
let val = waitFor callAsync(ff1(1))
print "future return", val.body
block:
let val = waitFor callAsync(ff2(1, 2))
print "future return", val.body