omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Should UI actions run in the background by default?

    Pythonista
    8
    13
    9499
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • omz
      omz last edited by

      This is a question for the more experienced folks here... I've lately been wondering whether ui actions should perhaps run in the background by default, instead of blocking the main thread. A question that comes up quite often is "why does x not work in a button action?".

      The reason that it works the way it does mostly has to do with consistency. For delegate-style callbacks, it is simply unavoidable to run on the main thread because the control is waiting for a response synchronously, e.g. a TableView needs to produce a cell immediately, a TextView needs to decide whether a character should be inserted, and will block the keyboard until it's done so, etc.

      On the other hand, using the action mechanism is much more common than delegates, especially for beginners. Since an action doesn't have to return anything, there's no real technical reason that it has to block everything else, and it makes a lot of common tasks more difficult, e.g. presenting a dialog when a button is tapped. Sure, you can usually just add @ui.in_background and be done with it, but I don't think that this is something a lot of beginners know (or should have to care) about, and decorators in general are not really something you see much when you start out learning Python...

      I can't really take the existing ui.in_background behavior and simply make it the default – the new one would have to work a bit differently because otherwise things like View.wait_modal would break. My idea would be to run all ui actions on a separate background thread/queue, and make ui.in_background simply do nothing (so that existing code that uses it doesn't break). I'm not even completely sure if this would work well, but I'd like to experiment with the idea.

      So, what do you think? Would this lead to inconsistency and more confusion? Would this break your existing code because you somehow rely on the current default behavior?

      1 Reply Last reply Reply Quote 0
      • dgelessus
        dgelessus last edited by

        Making action functions run in backgound by default sounds like a good idea. I can't think of any situation where it would be necessary to run an action in the foreground thread. If it were necessary for some reason, would it be possible to make a in_foreground decorator that does the opposite of in_background?

        Delegate methods should probably stay in the foreground thread by default though - it isn't possible to just run all of them in background, because some of them need to return something, and running some in fg and others in bg would be confusing IMO. In that case it would be easier to add an in_background decorator where necessary.

        1 Reply Last reply Reply Quote 0
        • wradcliffe
          wradcliffe last edited by

          You are asking a very difficult question that has been the subject of debate in UI architecture for a long time. The beginner programmer should be shielded from any need to know about threads and to have to deal with multithreaded programming. If you started running actions in different threads by default then you are just going to create a new problem that will be even harder to solve - teaching beginners how to write thread safe code.

          My 2 cents - leave it the way it is and add more samples and docs for the beginners. Throw exceptions for cases you can easily detect will have problems. Use it as an opportunity to educate the beginner on how to use decorators. Consider expanding the set of decorators that you provide in Pythonista to handle other common problems. We just had a discussion about logging for instance which could be implemented nicely with some trace and log decorators as one example. This takes you into the world of "aspects" and AOP (aspect oriented programming) and separation of concerns that would be of high value to any beginner programmer.

          1 Reply Last reply Reply Quote 0
          • Gerzer
            Gerzer last edited by

            How about implementing a warning outputted to the console when a UI action is executed on the main thread, explaining how to fix the problem, kind of like the warning you get when a UI element can’t find its assigned action? The warning would presumably not be an error, as that would break a lot of existing code not using @ui.in_background.

            1 Reply Last reply Reply Quote 0
            • polymerchm
              polymerchm last edited by

              My biggest "ask" is being able to call ui.input_alert in an action. Maybe I'm doing it wrong, but I've had to resort to building my own custom view to get input during an action.

              1 Reply Last reply Reply Quote 0
              • omz
                omz last edited by

                @polymerchm

                Does this work for you?

                import ui
                import console
                
                @ui.in_background
                def action(sender):
                	s = console.input_alert('Test')
                	sender.title = s
                	
                v = ui.Button()
                v.title = 'Tap me'
                v.action = action
                v.frame = (0, 0, 200, 200)
                v.present('sheet')
                
                1 Reply Last reply Reply Quote 0
                • polymerchm
                  polymerchm last edited by

                  Yes, but I mispoke. I meant to be able to do that as part of a "did_select" method of a tableview, not an action. Still doable?

                  1 Reply Last reply Reply Quote 0
                  • omz
                    omz last edited by

                    @polymerchm

                    This should work, if I understood you correctly:

                    import ui
                    import console
                    
                    class MyDataSource (ui.ListDataSource):
                    	@ui.in_background
                    	def tableview_did_select(self, tv, section, row):
                    		s = console.input_alert('Test')
                    		self.items[row] = s
                    
                    tv = ui.TableView()
                    tv.frame = (0, 0, 400, 400)
                    lds = MyDataSource(['foo', 'bar', 'baz'])
                    tv.data_source = lds
                    tv.delegate = lds
                    tv.present('sheet')
                    
                    1 Reply Last reply Reply Quote 0
                    • polymerchm
                      polymerchm last edited by

                      I need to rework some things as I thought from the docs that you could only inherit from ui.View. I inherit from object and a mixin class. Under those circumstances, the ui loop freezes and it only displays the input alert when the view is closed (with the upper left hand corner X). I presume I can override some of the default delegate and data_source UI.ListDataSource methods without doing any real damage?

                      1 Reply Last reply Reply Quote 0
                      • polymerchm
                        polymerchm last edited by

                        It does work. But only with the parent class as ui.ListDataSource. Then you need to remember to call the parent class's __init__ method to initialize some ui stuff as in:

                        class Capos(ui.ListDataSource,viewTools):  # viewTools is a "mixin" class.
                             def __init__(self,items):
                                 super(Capos,self).__init__(items)
                                         .
                                         .
                                        etc
                                         .
                        

                        Thanks, will simplify my interface. Got to finish current major update first.

                        1 Reply Last reply Reply Quote 0
                        • JonB
                          JonB last edited by

                          There are some actions, usually involving changing some ui state, where I seem to recall in_background causes unexpected results. I can't think of a specific example... While it is true that in_background does help avoid many ui crashes I share the concern that we will now have to learn the proper threadsafe practices.

                          1 Reply Last reply Reply Quote 0
                          • mteep
                            mteep last edited by

                            @omz
                            Like JonB, I have also experienced problems modifying UI state from an @in_background action. Similarly from any other threads, such as in ui.delayed functions or in callbacks from the cb module in the 1.6 beta. I think it happened when trying to append text to a TextView. But this is somewhat expected, since the underlying UIKit, like many other GUI toolkits, requires that most of its methods are called from the UI thread. So I believe something like the @in_foreground decorator dgelessus suggested is really needed already with the current model. (Unless I have misunderstood something.)

                            I would try this first, coupled with another decorator to use dispatch queues since, as you hinted, @in_background functions seems to be blocked by View.wait_modal(). Then, we could experiment with your idea without breaking anything.

                            But I suspect that using a serial queue (or a single thread) to process all actions would just cause it to block further actions from say a dialog. A better approach might be to do like AppKit and presumably UIKit, and run the event loop within the dialog presenting function, if called from the UI thread. Similar with View.wait_modal(). Or wouldn't that work?

                            1 Reply Last reply Reply Quote 0
                            • kollivier
                              kollivier last edited by

                              As someone who's been programming for over 15 years, I've found that I agree with the Zen of Python here that usually explicit is better than implicit. Also, in my experience, when faced with an issue like this, the best overall results are gained by trying to help the programmer to understand exactly what the code is doing rather than hide those details away. I know it's common that beginner programmers want to avoid getting caught up in the gory details, but when they eventually do hit problems that knowledge really helps them find the cause and fix it.

                              Is running in the background important for most actions, or just a few special (but common) scenarios, like triggering a dialog? I've coded in a few different GUI toolkits, and usually event handlers are run on the main thread. Modal dialogs blocking the app is, in other toolkits, expected behavior, as often in those cases you need a response from the user before the app can continue. If you don't want that behavior there is usually a separate option for showing dialogs non-blocking, where you send in a dialog finished callback function or a delegate.

                              1 Reply Last reply Reply Quote 0
                              • First post
                                Last post
                              Powered by NodeBB Forums | Contributors