jackhftang / threadproxy.nim

Simplify Nim Inter-Thread Communication
23 stars 0 forks source link

ThreadProxy with WebGui #1

Closed cgabriel5 closed 4 years ago

cgabriel5 commented 4 years ago

Hello @jackhftang. I'm currently building a GUI application (a package manager) and need a way to perform time consuming operations without freezing the main thread. While searching the Web I found this post where the OP was also in a similar situation. After reading your reply, ThreadProxy seems to be the missing link to perform said operations (i.e. updating/downloading packages) without blocking the main thread. In my case, I'm having trouble getting ThreadProxy working with WebGui.

The following are examples of nigui/webgui with threadproxy:

nigui Working Example (Both future callbacks run)
```nim import os, nigui, threadproxy proc workerMain(proxy: ThreadProxy) {.thread.} = proxy.onData "task1": let n = data.getInt() sleep n result = %"ThreadProxy Value" waitFor proxy.poll() proc main() = let proxy = newMainThreadProxy("main") proxy.createThread("worker", workerMain) app.init() var window = newWindow("Win") window.width = 600.scaleToDpi window.height = 400.scaleToDpi var container = newLayoutContainer(Layout_Vertical) window.add(container) var button = newButton("Start") var textArea = newTextArea() container.add(button) container.add(textArea) proc dummy_future(): Future[string] = var retFuture = newFuture[string]("dummy_future") sleep(1000) retFuture.complete("Future Value") return retFuture proc foo() = var fut = proxy.ask("worker", "task1", %1000) fut.addCallback( proc () = echo "[FutureValue]: ", fut.read ) var f = dummy_future() f.addCallback( proc () = echo "[FutureValue]: ", f.read ) foo() window.show() while true: app.processEvents() while proxy.process(): discard sleep 16 main() ``` The above code gives the expected output: ``` [FutureValue]: Future Value [FutureValue]: "ThreadProxy Value" ```
nigui Non-Working Example (ThreadProxy future callback never runs)
```nim import os, nigui, threadproxy proc workerMain(proxy: ThreadProxy) {.thread.} = proxy.onData "task1": let n = data.getInt() sleep n result = %"ThreadProxy Value" waitFor proxy.poll() proc main() = let proxy = newMainThreadProxy("main") proxy.createThread("worker", workerMain) app.init() var window = newWindow("Win") window.width = 600.scaleToDpi window.height = 400.scaleToDpi var container = newLayoutContainer(Layout_Vertical) window.add(container) var button = newButton("Start") var textArea = newTextArea() container.add(button) container.add(textArea) proc dummy_future(): Future[string] = var retFuture = newFuture[string]("dummy_future") sleep(1000) retFuture.complete("Future Value") return retFuture proc foo() = var fut = proxy.ask("worker", "task1", %1000) fut.addCallback( # <-------------- Never runs. proc () = echo "[FutureValue]: ", fut.read ) var f = dummy_future() f.addCallback( proc () = echo "[FutureValue]: ", f.read ) foo() window.show() while true: app.run() while proxy.process(): discard sleep 16 main() ``` `proxy.ask` future callback never runs so the output is only: ``` [FutureValue]: Future Value ```
webgui Non-Working Example (ThreadProxy future callback never runs)
```nim import os, webgui, threadproxy proc workerMain(proxy: ThreadProxy) {.thread.} = proxy.onData "task1": let n = data.getInt() sleep n result = %"ThreadProxy Value" waitFor proxy.poll() proc main() = let proxy = newMainThreadProxy("main") proxy.createThread("worker", workerMain) let app = newWebView(""" """ ) proc dummy_future(): Future[string] = var retFuture = newFuture[string]("dummy_future") sleep(1000) retFuture.complete("Future Value") return retFuture proc foo() = var fut = proxy.ask("worker", "task1", %1000) fut.addCallback( # <-------------- Never runs until after application window is closed. proc () = echo "[FutureValue]: ", fut.read ) var f = dummy_future() f.addCallback( proc () = echo "[FutureValue]: ", f.read ) foo() # This does not seem right... while true: app.run() while proxy.process(): discard sleep 16 app.exit() main() ``` `proxy.ask` future callback never runs so the output is only: ``` [FutureValue]: Future Value ```

In short, the ThreadProxy proxy.ask future callback does not run in the WebGui example. It's almost as it if hangs because once the application window closes the callback finally runs.

...
var fut = proxy.ask("worker", "task1", %1000)
fut.addCallback( # <-------------- Never runs until after application window is closed.
    proc () =
        echo "[FutureValue]: ", fut.read
)
...

Looking more into your reply (nigui example) I noticed you used app.processEvents() instead of app.run(). This change is the difference between the working and non-working nigui examples. Where the working example uses app.processEvents(). This is interesting because all nigui examples make use of app.run(). When it comes to WebGui, I don't see something akin to nigui's app.processEvents(), which is where I think the issue lies.

ThreadProxy is a really cool idea to leverage threads but since I'm new to async/futures/threads in Nim I'm having trouble implementing it with WebGui. Which is why I thought I'd reach out. If you could give me your insight here to get ThreadProxy running alongside WebGui, if at all possible, it would be much appreciated. Thank you.

Miscellaneous:

jackhftang commented 4 years ago

In short, you should use app.processEvents()

This is because nigui has its own event loop and threadproxy also has its own event loop.

app.run() is like

while true:
  app.processEvents()

app.run will block the main thread. And after you close the application window, run() break and then run proxy.process() and that is why you see console output after you closed the window.

If you only need to process the events coming from nigui e.g. mouse clicks, closing windows... then calling app.run() is enough. However as mentioned before threadproxy has its own event loop. You have to call proxy.process() to process one event or proxy.poll() and then start nim async event loop runForever().

This part of code fuses the event loop of both nigui and threadproxy.

while true:
    app.processEvents()
    while proxy.process():
      discard
    sleep 16
cgabriel5 commented 4 years ago

Yeah, I suspected app.run() was blocking ThreadProxy. WebGui doesn't have something similar to nigui's app.processEvents(), so I need to figure out a way to fuse WebGui's and ThreadProxy's event loops. Thank you for taking the time to reply.