Threading and ui objects from pyui files and array notation
Sorry in advance if this is a Python question Vrs a Pythonista question. I am just not sure.
I have a cell class (a view to display information) that Inherits from both ui.View and my own class.
Anyway, this seems to work well for the most part. But when I use a pyui file as the view for the cell, I am accessing the objects with array notation. Again this works as expected.
But in threaded methods, array notation fails. Maybe it's something about early and late binding, I don't understand. Maybe it should work. I am not sure.
I put some code below, but I know it's difficult because it's only paritial , and does not run by itself.
Any ideas help appreciated. Oh, I can get object references to elements in the pyui file. But need to make an attribute in the cell class to access it before entering the thread.
# cell we are using in the VirtualView class TestCellVirtualViewCell2(ui.View , CellBase): def __init__(self, w, h, item_index, Threaded = False, auto_start_thread =True): CellBase.__init__(self, w, h , item_index, threaded = Threaded, thread_auto_start = auto_start_thread) self.cust_view = None self.add_item_id_label() # This method is not threaded, is overrided method # from Cellbase. is called automatically. def create_cell_contents(self): self.cust_view = ui.load_view('mycell1') self.cust_view.frame = self.frame self.add_subview(self.cust_view) self.cust_view.border_width = .5 self.cust_view['lb'].text = 'hello world' self.cust_view['img1'].image = rand_image() # this a limitation i dont understand. # in the thread, i can not access # self.cust_view['img2']. i am sure its easy # to know why, but i dont know why. self.img2 = self.cust_view['img2'] self.start_activity() # this method is overridden from Cellbase and # is called on a thread. is called from Cellbase def fetch_data(self): # simulate loading a web resource time.sleep(random.random()) # this is a method in Cellbase class. # a way to exit the thread if self.thread_check():return time.sleep(random.random()) if self.thread_check():return time.sleep(random.random()) if self.thread_check():return if self.thread_check():return self.img2.image = rand_image() # in the non threaded code, i can access # self.cust_view['lb'].text, in this method, # creates an exeception. basically, # self.cust_view['lb'] evaluates to None #self.cust_view['lb'].text = 'Loaded' self.stop_activity()```
You will probably need to provide a little more context. Where is fetch data called? Where is create_cell_contemts called? Who is is adding img2 as a subview, and in what method? What is that thread_check function doing?
If both of these methods are called from within init, your self.custdata= None at the end of init is problematic.... you are removing the reference to the data before you try to access it in the Thread.
This type of problem is typically handled with something like a Semaphore or Lock. If you have two threads, and one depends on the result from the other, you would have the dependent thread block, using a Lock or Semaphore, until the first thread is finished doing what it needs to. The first thread then signals (by releasing the lock) that it is complete. Note that things running within the ui main thread are doing things on their own timeline. Also not that if you use
ui.in_backgroundin conjunction with Threads, you will be sorely disappointed -- that does not create a new thread for each call, so things may not get done in the order you expect. See the SPLView i posted recently in a thread recently, or in my uicomponents repo, for an async_exec decorator that does create a new thread.
The other thought i have is that you may need a Semaphore not just for synchronization, but need a Lock for keeping the code nonreentrant. If one or both of these methods is kicked off by a ui event, like
scrollview_did_scroll, or touch events, this code could get called multiple times in parallel! if you want to queue up calls, you would use a lock.acquire() at the start of the method, and a lock.release() at the end. If you want to ensure that you only kick off fetch data once (say, if the user scrolls onto then off of the screen, but you still want to keep loading), tou would use the nonblocking version of acquire, check the return type, then return early. The SPLView has an example of this (if i was updating the matplotlib and another touch event came in, i kept track of the cumulative touch, but did not reupdate the matplotlib image.
@JonB, thanks for all the info. Trying to digest it all. But as far as I can see my so called 'state machine' will not be reenterant. At least I don't think so. In the example I post now in a gist, is the full class, just no stress testing on it. The cell class, I have really hammered it, all appears ok. Created 100,000's of cells in the virtual view with it. Does not fail. No memory leaks. If the code try's to create a new thread while one isAlive, it exits. Maybe some things I don't understand. But here all that is going on is I can not reference a ui objects in the thread by using array notation. As I want the code to be bullet proof, i just am trying to figure out why. There is a work around. Create a dynamically created attribute outside the thread. I am guessing it's something to do with bound and unbound or early or late binding. I am just guessing. It's a hard topic to research.
The base class initialised the
cust_viewproperty but then it got set to
Noneright after. That is why
None. It is not a threading issue.
Yup. There is also a problem in release. If your thread takes a very long time to run, or has a long time between checking thread_check, (for instance, i imagine your purpose is to load some web resource... you cannot necessarily interrupt a urllib2 call which is waiting on the network), at the end of release, you delete the subview out from under it.
You probably need to start another thread which waits for the new one to finish, (by using a blocking semaphore or lock) before deleting, or at least wrap the threaded use of any subviews in a try/except although this would have hidden your original problem.
@ywangd , really thanks so much. All I did was comment out line 324, then it all works as expected. I really don't know why, but I will find out. Sorry about wasting time. I honestly did not know if it was Pythonista specific or not! As it turns out, my lack of understanding
@JonB , good point. I tried with a 2 min load time in my virtual view. Does not take long to crash. I will look at join and lock, rlock etc. again, thanks for your help on a non pythonista specific problem. But I would like to get it right and share here. But my feeling is when indie programmer @omz gets a spare weekend , he will put his mind to it and it will all be built into the ui.TableView in a professional way :)