Synchronous and Asynchronous callbacks

Barry Scott barry at barrys-emacs.org
Mon Sep 30 05:09:06 EDT 2019



> On 29 Sep 2019, at 21:41, Eko palypse <ekopalypse at gmail.com> wrote:
> 
> Am Sonntag, 29. September 2019 19:18:32 UTC+2 schrieb Barry Scott:
>>> On 29 Sep 2019, at 14:14, Eko palypse <ekopalypse at gmail.com> wrote:
>>> 
>>> Unfortunately, I can't make all callbacks synchronous or asynchronous because this has negative effects on the application in one way or another.
>> 
>> Surely you can make all callbacks async?
>> You do not have to have them wait, they can complete their work in one call.
>> 
>> sync == async-that-does-not-await
>> 
>> Barry
> 
> Thank you for your answer but I'm sorry I don't get it. 
> What do you mean by I don't have to have them wait?
> 
> Let me explain my understanding, with some pseudo code, how async callbacks
> works and please keep in mind that I'm a novice when it comes to OO designs
> and basic software design strategies/techniques, so I might be totally
> wrong about what I'm doing.

When you said async I thought you meant the specific programming model in Python
that uses "async def", "await" etc. You are using threads.

The problem you are facing is that the UI must not become unresponsive.
Further a background thread cannot call any UI framework functions as the UI
framework can only be called on the main thread.

You can run fast event handers on the UI thread but slow running handlers
must be run on another thread to allow the UI to be responsive.

What I do is have the event handler for a long running task take the responsibility
to queue the task onto a background thread. While the task is running it sends status
to the UI thread so that UI elements are updated (progress bars, etc).

If you are using PyQt5 you might find the code I used for SCM Workbench interesting.

I can write event handlers that look like this:

@thread_switcher
def eventHandler( self, ... ):
	# starts in the foreground (UI thread)
	self.status.update('Working...')

	yield self.switchToBackground
	# now on the background thread
	self.doSlowTask()

	yield self.switchToForeground
	# now in the foreground
	self.status.update('Done')

The code is in:

https://github.com/barry-scott/scm-workbench/blob/master/Source/Common/wb_background_thread.py

@thread_switcher adds an attribute to the function so the rest of the
code knows this is an event handler that needs to use a background thread.

When I add an event handler I call through a function (wrapWithThreadSwitcher)
that wraps only functions that have been marked with @thread_switcher.

The rest of the code is the machinery to handle moving between threads via the generator.

Barry

> 
> An SYNCHRONOUS callback is like this. Event/notification gets fired and
> notification handler calls all registered methods one after the other.
> Something like
> 
> if notification.list_of_methods:
>    for method in list_of_methods:
>        method()
> 
> whereas an ASYNCHRONOUS callback is more like this. Event/notification gets
> fired and notification handler informs another thread that the event has
> been fired and all registered async callback methods should get executed.
> So something like this
> 
> class EventThread(threading.Thread):
>    def __init__(...):
>        super()...
>        self.event = threading.Event()
>        self.kill = False
>        ...
>    def run(self):
>        while True:
>            self.event.wait()
>            self.event.clear()
>            if not self.kill:
>                for method in self.list_of_methods:
>                    method()
> 
> et = EventThread()
> if notification.list_of_methods:
>    et.event.set()  # run all async methods
>    for method in list_of_methods:  # run sync methods
>        method())
> 
> 
> So if there is no synchronous callback for that notification then the 
> notification handler would just set the event and return. 
> The EventThread would then call one registered callback after the other.
> Right?
> 
> Using this approach does sound fine from UI point of view as there is
> minimal impact and UI keeps responsive but from code execution point of view
> it sound much more complicated. What if one async callback is slow or buggy?
> This could lead to unpredictable behavior where as in a synchronous execution you immediately know what is causing it. 
> That was the reason I decided not to offer async callbacks until I 
> discovered that other parts, like UI modifications aren't working properly.
> And there are notification which must be called in a synchronous way, like
> the modification event. If you want to intercept a modification safely then it can only be done within a synchronous callback. 
> 
> Does this makes sense to you? Or do I have wrong understanding or, again, an
> wrong assumption how this works?
> 
> Thank you
> Eren
> -- 
> https://mail.python.org/mailman/listinfo/python-list
> 




More information about the Python-list mailing list