wxasync versus wx yield

classic Classic list List threaded Threaded
12 messages Options
Reply | Threaded
Open this post in threaded view
|

wxasync versus wx yield

Brendan Simon (eTRIX)
I've been playing with `wxasync` and think it's fantastic, however I've also just discovered wx.Yield (deprecated) and friends -- wx.GetApp().Yield() wx.SafeYield(), wx.YieldIfNeeded()

Am I right in assuming that these Yield functions will allow the wx mainloop to process pending events and redraw windows/controls if required?

If so, does this mean my use case of running a longer task, and updating some controls in the process, can also be achieved in an event handler and using wx Yield to update the GUI? (e.g. downloading a file to a usb device)

I was under the impression that long running tasks needed to be handled in a thread (I had been using wx.lib.delayedresult producer/consumer functions in the past, which I found a little painful).

I'm sure there are some advantages to using wxasync (e.g. waiting for IO and such), but for simple use cases wx Yield might to the job just as well (and presumably also works with python < 3.5).  Also one less python module dependency to manage.

Thanks,
Brendan.

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

Tim Roberts
On Jul 14, 2018, at 7:41 PM, Brendan Simon (eTRIX) <[hidden email]> wrote:

Am I right in assuming that these Yield functions will allow the wx mainloop to process pending events and redraw windows/controls if required?

Sort of.  wx.Yield basically runs a temporary message loop long enough to drain any pending messages.


If so, does this mean my use case of running a longer task, and updating some controls in the process, can also be achieved in an event handler and using wx Yield to update the GUI? (e.g. downloading a file to a usb device)

Yes, but remember that your GUI will be frozen in between calls to wx.Yield.  Downloading a file can take a long time, and that results in a very poor user experience.  You could not, for example, have a "Cancel" button to cancel the transfer.


I was under the impression that long running tasks needed to be handled in a thread (I had been using wx.lib.delayedresult producer/consumer functions in the past, which I found a little painful).

That IS the correct method.  wx.Yield is a hack, left over from the 16-bit Windows days.
— 
Tim Roberts, [hidden email]
Providenza & Boekelheide, Inc.

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

Brendan Simon-3

On Monday, 16 July 2018 11:48:45 UTC+10, Tim Roberts wrote:
On Jul 14, 2018, at 7:41 PM, Brendan Simon (eTRIX) <<a href="javascript:" target="_blank" gdf-obfuscated-mailto="FZ5r7QNXAgAJ" rel="nofollow" onmousedown="this.href=&#39;javascript:&#39;;return true;" onclick="this.href=&#39;javascript:&#39;;return true;">brenda...@...> wrote:
Am I right in assuming that these Yield functions will allow the wx mainloop to process pending events and redraw windows/controls if required?

Sort of.  wx.Yield basically runs a temporary message loop long enough to drain any pending messages.

If so, does this mean my use case of running a longer task, and updating some controls in the process, can also be achieved in an event handler and using wx Yield to update the GUI? (e.g. downloading a file to a usb device)

Yes, but remember that your GUI will be frozen in between calls to wx.Yield.  Downloading a file can take a long time, and that results in a very poor user experience.  You could not, for example, have a "Cancel" button to cancel the transfer.

I was under the impression that long running tasks needed to be handled in a thread (I had been using wx.lib.delayedresult producer/consumer functions in the past, which I found a little painful).

That IS the correct method.  wx.Yield is a hack, left over from the 16-bit Windows days.
 

Thanks for your response Tim.  Ok, sounds like wx.Yield would work if called regularly enough to update the GUI.  Mind you, the same thing has to happen if using wxAsync/ascyncio (i.e. you have to yield to allow other tasks to run, one of which is  wxAsyncApp which is running the wx mainloop).  e.g. using "await asyncio.sleep(0)" or awaiting on some other coroutine that yields.

Since wx.Yield is considered a hack (is that a general consensus?) I will opt for using wxAysnc as my goto architecture for most things, and only look to using threads if absolutely necessary.  There's lots of examples of using asyncio with threads too (with asyncio having some "*_threadsafe()" functions, etc), so it's an easy jump to migrate to using threads (not using wx.lib.delayedresult)

Cheers, Brendan.

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

Tim Roberts
Brendan Simon wrote:


Since wx.Yield is considered a hack (is that a general consensus?) ...

Ah, we programmers are such a judgmental crew.

It's not really a "hack", but it is definitely considered "outdated".  In a modern, responsive app, all UI tasks should be quick, quick quick.  Anything that takes long enough to require a wx.Yield should rightly be handled in another thread.

So, you can use it, but you are required to feel bad about it.   ;)
-- 
Tim Roberts, [hidden email]
Providenza & Boekelheide, Inc.

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

Brendan Simon-3


On Thursday, 19 July 2018 10:27:06 UTC+10, Tim Roberts wrote:
Brendan Simon wrote:
Since wx.Yield is considered a hack (is that a general consensus?) ...

Ah, we programmers are such a judgmental crew.

It's not really a "hack", but it is definitely considered "outdated".  In a modern, responsive app, all UI tasks should be quick, quick quick.  Anything that takes long enough to require a wx.Yield should rightly be handled in another thread.

So, you can use it, but you are required to feel bad about it.   ;)

Ok.  So I revisited threads thanks to your "encouragement" Tim ;-)

At the end of the "Long Running Tasks" wiki (https://wiki.wxpython.org/LongRunningTasks) there is a simple example of using threads along with wx.CallAfter().

That looks a little simpler (less boiler plate) than using `wx.lib.delayedresult`.  I actually converted it to use the `threading` module instead of the older `thread` module and it works great.  If there are lots of gui updates required then I encapsulate them in a function and call the update function via wx.CallAfter().

Now, is there any advantages to using `delayedresult`?  It requires a `producer` and `consumer` it would seem.  What's the point of the consumer?  Surely the producer can do whatever it needs to at the end (e.g. save some state, results, or update the GUI, etc).  Is the consumer nothing more than another function called by wx.CallAfter()?

The only thing I can think of is that for worker threads that do not require any GUI interaction during the process, can just return a result and the producer can then cleanup and update the GUI.  i.e. the worker thread does not need to bother with wx.CallAfter() calls within it.

Thinking about it out loud (a little more), it might be useful for calling other executables that do a particular job but are not python modifiable (e.g. an binary, script, read-only python module, etc)

wxAsync still feels nice from a wx perspective (none of the "ugly" wx.CallAfter() statements), however it does rely on the asycio tasks playing nicely and "yielding" often enough to let the wx.App main loop run.

Many ways to skin a cat it would seem.  Now I'm thinking using the `threading` module might be my goto architecture, and possibly `delayedresult` if I need to run an external executable?



--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

Robin Dunn
On Friday, July 20, 2018 at 6:48:06 AM UTC-7, Brendan Simon wrote:
Many ways to skin a cat it would seem. 

Indeed.
 
Now I'm thinking using the `threading` module might be my goto architecture, and possibly `delayedresult` if I need to run an external executable?


I don't think delayedresult has any special advantage for running external executables, it's just another cat-skinner. Or IOW, just another way to deal with long-running tasks, whatever they may be. It just happens to be a better fit for some people and how they like to do things. IMO delayedresult is good if you like the pattern of "start something and keep on going while the something is percolating, when it's done send the result to something else." It's similar to a fairly common pattern sometimes associated with names like Future, Deferred, Promise, etc.
 
--
Robin

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

Brendan Simon-3
 
Now I'm thinking using the `threading` module might be my goto architecture, and possibly `delayedresult` if I need to run an external executable?


I don't think delayedresult has any special advantage for running external executables, it's just another cat-skinner. Or IOW, just another way to deal with long-running tasks, whatever they may be. It just happens to be a better fit for some people and how they like to do things. IMO delayedresult is good if you like the pattern of "start something and keep on going while the something is percolating, when it's done send the result to something else." It's similar to a fairly common pattern sometimes associated with names like Future, Deferred, Promise, etc.
 
Thanks Robin.  That makes sense re delayedresult.

My usual use case is donwloading/uploading info from usb devices, with a progress bar, so `delayedresult` doesn't fit as nicely (of course it does work, but the callback to the consumer is redundant).

The advantage of wxasync is I can call wx methods directly in asyncio tasks, as they run in the same thread, whereas I need to use wx.CallAfter() when using threads.

One caveat with wx.CallAfter is you can't use widget properties - you have to use a callable.  e.g. wx.CallAfter( myButton.Label, "g'day" ) needs to be wx.CallAfter( myButton.SetLabel, "g'day" )

wx.CallAfter() scattered about is little ugly (to me), but not a showstopper by any means.  So is there a convenient/clever way to use wx.CallAfter() without explicitly calling it (e.g. via some kind of decorator?).  I did use wxAnyThread in the past with an app, but somewhere along the way it had a performance impact and I swapped to wx.CallAfter.

Best I can come with is a wrapper function/method to update widgets of interest.

def update_progress( self, value=None, range=None ):
   
if value != None:
       
self.gauge.Value = value

   
if enable != None:
       
self.gauge.Range = range

def update_progress_threadsafe( self, value=None, range=None ):
    wx
.CallAfter( self.update_progress, label, range )

Is there some kind of decorator I can apply to `update_progress` so I can call it directly from a thread, without having to write a wrapper?

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

Brendan Simon-3
In reply to this post by Robin Dunn
 
Now I'm thinking using the `threading` module might be my goto architecture, and possibly `delayedresult` if I need to run an external executable?

I don't think delayedresult has any special advantage for running external executables, it's just another cat-skinner. Or IOW, just another way to deal with long-running tasks, whatever they may be. It just happens to be a better fit for some people and how they like to do things. IMO delayedresult is good if you like the pattern of "start something and keep on going while the something is percolating, when it's done send the result to something else." It's similar to a fairly common pattern sometimes associated with names like Future, Deferred, Promise, etc.

Thanks Robin.  That makes sense re delayedresult.

My usual use case is donwloading/uploading info from usb devices, with a progress bar, so `delayedresult` doesn't fit as nicely (of course it does work, but the callback to the consumer is redundant).

The advantage of wxasync is I can call wx methods directly in asyncio tasks, as they run in the same thread, whereas I need to use wx.CallAfter() when using threads.

One caveat with wx.CallAfter is you can't use widget properties - you have to use a callable.  e.g. wx.CallAfter( myButton.Label, "g'day" ) needs to be wx.CallAfter( myButton.SetLabel, "g'day" )

wx.CallAfter() scattered about is little ugly (to me), but not a showstopper by any means.  So is there a convenient/clever way to use wx.CallAfter() without explicitly calling it (e.g. via some kind of decorator?).  I did use wxAnyThread in the past with an app, but somewhere along the way it had a performance impact and I swapped to wx.CallAfter.

Best I can come with is a wrapper function/method to update widgets of interest.

def update_progress( self, value=None, range=None ):
   
if value != None:
       
self.gauge.Value = value

   
if range != None:
       
self.gauge.Range = range

def update_progress_threadsafe( self, value=None, range=None ):
    wx
.CallAfter( self.update_progress, value=value, range=range )

Is there some kind of decorator I can apply to `update_progress` so I can call it directly from a thread, without having to write a wrapper?

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

Brendan Simon-3
On Saturday, 21 July 2018 22:11:47 UTC+10, Brendan Simon wrote:
I did use wxAnyThread in the past with an app, but somewhere along the way it had a performance impact and I swapped to wx.CallAfter.

So I actually looked at the source code for wxAnyThread to see how it works.  There's one aspect of it that I never realiased.  The thread calling a wxAnyThread decorated function will block, until the main gui event loop processes the function and it returns a result back the calling thread.  I suspect that might be the reason for the performance hit I noticed in the past (and probably was always there).

That seems suitable to some use cases (like the wxAnyThread example that shows a dialog to get some user input), but is not as efficient if just wanting to send some data to update a gauge or other controls (i.e. don't want the thread to block as the gui can update the controls in it's own time).

Stealing bits from wxAnyThread, would the following work (as a decorator)?

def callafter( func ):
   
""""
   
Method decororator allowing call from any thread

    When invoked from the main thread, the function is executed immediately.
   
"""

    def invoker( *args, **kwds ):
        if wx.Thread_IsMain():
           return func( *args, **kwds )
        else:
           return wx.CallAfter( func, *args, **kwds )

 
    invoker.__name__ = func.__name__
    invoker.__doc__  = func.__doc__
    return invoker

Disclaimer: I've never written a decorator :)

Would calling wx.CallAfter even in the main GUI thread cause delays?  e.g. would the main loop have to wait to be run again before processing the function?  i..e could I get away without the test wx.Thread_IsMain() ?

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

Brendan Simon-3

On Saturday, 21 July 2018 23:05:17 UTC+10, Brendan Simon wrote:

Stealing bits from wxAnyThread, would the following work (as a decorator)?

Seems to work (though I couldn't get the GUI to crash on my mac when not using the decorator and calling the update functions from a thread).

I modified it slightly (no return values, add wx.Yield).  It works so I can call the same task from the main thread (and have the GUI update) or another thread.

Are there any issues that I'm unaware of ???

def callafter( func ):
   
""""
   
Method decororator allowing call from any thread

   
When invoked from the main thread, the function is executed immediately.
   
"""

    def invoker( *args, **kwds ):
        if wx.IsMainThread():
            #print("
call func directly from main thread")
            func( *args, **kwds )
            wx.Yield()
        else:
            #print("
call func via wx.CallAfter from non-main thread")
            wx.CallAfter( func, *args, **kwds )

    invoker.__name__ = func.__name__
    invoker.__doc__  = func.__doc__
    return invoker





--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

André Colomb
This is the construct I am using successfully in my app:

|
import wx
import functools


def gui_call(func):
    """Decorator for a callable to be invoked on the main GUI thread"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return wx.CallAfter(func, *args, **kwargs)
    return wrapper
|

I don't use it as a decorator though, because I usually need it as an
inline expression:

|
callable = gui_call(myButton.SetLabel, "g'day")
|

Hope it helps.

Kind regards
André

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: wxasync versus wx yield

Tim Roberts
In reply to this post by Brendan Simon-3
On Jul 21, 2018, at 6:05 AM, Brendan Simon <[hidden email]> wrote:

Would calling wx.CallAfter even in the main GUI thread cause delays?  e.g. would the main loop have to wait to be run again before processing the function?  i..e could I get away without the test wx.Thread_IsMain() ?

wx.CallAfter puts a message in the message queue.  If that queue had previously been empty, it then fires an event, on the theory that the main loop was blocked waiting for a "new message" event.  Firing hat event will make the thread "ready to run", so it should get picked the next time the scheduler runs on an available CPU.  If you have multiple CPUs, that happens very quickly.
— 
Tim Roberts, [hidden email]
Providenza & Boekelheide, Inc.

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.