Runtime: work around 1s delay added when using twisted reactor's callLater. wxPython4
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Wed, 05 Oct 2022 16:10:17 +0200
branchwxPython4
changeset 3617 c3aae4c95bc1
parent 3616 369c3edc1a65
child 3618 f08b59f7c00a
Runtime: work around 1s delay added when using twisted reactor's callLater.

Since wxPython4, using wxReactor from non-main thread was producing
exceptions in wxWidget's C++ code. Then reactor.run() was called from
main thread, and runtime's worker was delegating calls to reactor
with callLater(0, callable).

While this worked perfectly with wxReactor, it did introduce an unexplained
1 second delay to each worker call when using nomal linux reactors
(i.e. without wxPython). As a workaround reactor runs in a thread when using
twisted without wxPython
Beremiz_service.py
--- a/Beremiz_service.py	Wed Sep 14 15:24:33 2022 +0200
+++ b/Beremiz_service.py	Wed Oct 05 16:10:17 2022 +0200
@@ -423,7 +423,12 @@
             if havewx:
                 from twisted.internet import wxreactor
                 wxreactor.install()
-            from twisted.internet import reactor
+                from twisted.internet import reactor
+                reactor.registerWxApp(app)
+            else:
+                # from twisted.internet import pollreactor
+                # pollreactor.install()
+                from twisted.internet import reactor
 
             havetwisted = True
         except ImportError:
@@ -432,11 +437,6 @@
 
 pyruntimevars = {}
 
-if havetwisted:
-    if havewx:
-        reactor.registerWxApp(app)
-
-
 if havewx:
     wx_eval_lock = Semaphore(0)
 
@@ -578,26 +578,50 @@
 
     runtime.GetPLCObjectSingleton().AutoLoad(autostart)
 
-if havetwisted or havewx:
-
-    waker_func = wx.CallAfter if havewx else partial(reactor.callLater,0)
+if havetwisted and havewx:
+
+    waker_func = wx.CallAfter
 
     # This orders ui loop to signal when ready on Stdout
     waker_func(print,"UI thread started successfully.")
 
-    # worker that copes with wx and (wx)reactor
+    # interleaved worker copes with wxreactor by delegating all asynchronous
+    # calls to wx's mainloop
     runtime.MainWorker.interleave(waker_func, FirstWorkerJob)
 
     try:
-        if havetwisted:
-            reactor.run(installSignalHandlers=False)
-        else:
-            app.MainLoop
+        reactor.run(installSignalHandlers=False)
     except KeyboardInterrupt:
         pass
 
     runtime.MainWorker.stop()
 
+elif havewx:
+
+    try:
+        app.MainLoop
+    except KeyboardInterrupt:
+        pass
+
+elif havetwisted:
+
+    ui_thread_started = Lock()
+    ui_thread_started.acquire()
+
+    reactor.callLater(0, ui_thread_started.release)
+
+    ui_thread = Thread(
+        target=partial(reactor.run, installSignalHandlers=False),
+        name="UIThread")
+    ui_thread.start()
+
+    ui_thread_started.acquire()
+    print("UI thread started successfully.")
+    try:
+        # blocking worker loop
+        runtime.MainWorker.runloop(FirstWorkerJob)
+    except KeyboardInterrupt:
+        pass
 else:
     try:
         # blocking worker loop
@@ -618,6 +642,8 @@
 
 if havetwisted:
     reactor.stop()
+    if not havewx:
+        ui_thread.join()
 elif havewx:
     app.ExitMainLoop()