pywebview icon indicating copy to clipboard operation
pywebview copied to clipboard

Add `block` parameter to `webview.start` using multiprocessing

Open louisnw01 opened this issue 1 year ago • 11 comments

Description

Hey @r0x0r,

I'd like to request an implementation of a non-blocking webview window, which runs the webview in a seperate process. This would eliminate the need for a callback function in webview.start, and would greatly increase the flexibility of this library.

A basic example:

window = webview.create_window()

webview.start(block=False)

window.evaluate_js("console.log('hello')")

while 1:
    sleep(1)

I implemented a very primitive and specialized version of this for my library; here is the source: https://github.com/louisnw01/lightweight-charts-python/blob/main/lightweight_charts/chart.py

I was running into a few issues with that, and I remember you mentioning that this was in the works, so I just wanted to bring it to your attention!

Thanks!

Louis

louisnw01 avatar May 16 '24 10:05 louisnw01

The problem with multiprocessing is that a Window object cannot be serialised because of threading events. Unability to serialise Window is rather a show stopper. Non-blocking start() can be implemented without multiprocessing on all the platforms but Cocoa. I have tried to implement this, but could not find an adequate solution. Help is welcomed.

r0x0r avatar May 16 '24 20:05 r0x0r

The problem with multiprocessing is that a Window object cannot be serialised because of threading events. Unability to serialise Window is rather a show stopper. Non-blocking start() can be implemented without multiprocessing on all the platforms but Cocoa. I have tried to implement this, but could not find an adequate solution. Help is welcomed.

Why do you need to serialise the Window object?

Louis

louisnw01 avatar May 16 '24 20:05 louisnw01

You might want to access a Window objects in your application code. For that you need to pass them between processes and they need to be serialised first.

r0x0r avatar May 16 '24 20:05 r0x0r

You might want to access a Window objects in your application code. For that you need to pass them between processes and they need to be serialised first.

What I mean is the Window object (in its current state) can sit in the other process.

Then another 'window' object (which would replace the old one) could be used which only communicates with the other object running in the other process

louisnw01 avatar May 16 '24 20:05 louisnw01

Essentially all you would need to write is a wrapper for a new window class, and a controller which sits in the other process and keeps everything up to date

louisnw01 avatar May 16 '24 20:05 louisnw01

I am afraid I do not follow. How would you rewrite following code in multiprocessing? This is identical to passing a function with a parameter to start

from time import sleep
from threading import Thread
import webview

"""
Loading new HTML after the window is created
"""


def load_html(window):
    sleep(5)
    window.load_html('<h1>This is dynamically loaded HTML</h1>')


if __name__ == '__main__':
    window = webview.create_window('Load HTML Example', html='<h1>This is initial HTML</h1>')

    t = Thread(target=load_html, args=(window,))
    t.start()
    webview.start()

r0x0r avatar May 16 '24 20:05 r0x0r

I am afraid I do not follow. How would you rewrite following code in multiprocessing? This is identical to passing a function with a parameter to start

from time import sleep
from threading import Thread
import webview

"""
Loading new HTML after the window is created
"""


def load_html(window):
    sleep(5)
    window.load_html('<h1>This is dynamically loaded HTML</h1>')


if __name__ == '__main__':
    window = webview.create_window('Load HTML Example', html='<h1>This is initial HTML</h1>')

    t = Thread(target=load_html, args=(window,))
    t.start()
    webview.start()

Something like this:

from time import sleep
from threading import Thread
import webview


#
#  this runs in a seperate process
#
class ActualWindow:
    
    ...

    def loop(self):
        while 1:
            method_name, args = self.mp_queue.get()
            method = getattr(self, method_name)
            method(*args)


#
#  dummy Window class, which is instansiated in the main process
#
class Window():
    
    ...

    def load_html(self, html: str):
        self.mp_queue.put(('load_html', (html)));


if __name__ == '__main__':
    window = webview.create_window('Load HTML Example', html='<h1>This is initial HTML</h1>')

    webview.start(block=False)

    sleep(5)
    window.load_html('<h1>This is dynamically loaded HTML</h1>')


louisnw01 avatar May 16 '24 21:05 louisnw01

The thing is pywebview itself is using window objects internally. In your example you create a window object in the main process. How do you pass it to the process in webview.start()?

r0x0r avatar May 17 '24 06:05 r0x0r

The thing is pywebview itself is using window objects internally. In your example you create a window object in the main process. How do you pass it to the process in webview.start()?

The 'actual' window object is the one running in the other process ie the same process that webview is running in.

The only communication between the two processes is the 'dummy' classes or methods that send over a queue, which control the actual classes instantiated in the other process

louisnw01 avatar May 17 '24 19:05 louisnw01

If all that is needed is a non-blocking webview.start(), then why not just keep the current threading implementation, but instead of running the backend logic callback in a thread, just create the main window in a thread and return immediately, allowing the backend logic to be implemented in the main thread? Considering that webview.start() will already start all windows but the first in new threads, it's even a bit unexpected that it starts the first one in the main thread, requiring a separate callback for the backend logic to run that in a thread.

lanzz avatar May 18 '24 15:05 lanzz

@louisnw01 honestly I don't know if it is possible to split Window object in such a way. If you or somebody else get it working keeping API the same I can accept a PR.

@lanzz Cocoa requires GUI to be run on the main thread. Other platforms can be run in a thread as you described.

r0x0r avatar May 19 '24 20:05 r0x0r

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Jun 19 '24 02:06 github-actions[bot]

The message to post on the issue when closing it. If none provided, will not comment when closing an issue.

github-actions[bot] avatar Jun 24 '24 02:06 github-actions[bot]