Concurrency: DataSource modified while TableView is displaying data
Hi All, I have an NSTableView whose DataSource and delegate is a custom controller holding an NSMutableArray (containing a list of "Task/job" objects). The tableView is used to display "Tasks" waiting to be executed. When activated, the "Task" is requested to post its blocks on a serial queue by the controller. The last block posted by all tasks will actually notify (objC message) the controller that the task has ended. The controller will remove the task from the list and request the NSTableView to reload its data. This, I presume, is executed from the serial queue. Sometimes, I get an out of bounds exception from my data source (the NSMutableArray owned by the controller). I am thinking: The issue is that, sometimes, between the time the NSMutableArray is requested to remove an object and the time the tableView is requested to reload its data, some external event (rare) may trigger a redraw. The tableView, unaware that one element is now missing, requests from the data source an element whose row number is equal to the datasource's count (=> out of bounds by 1 index). What's troubling me: When stepping through the programming with the debugger, I plainly see that the tableView has requested the number of elements in the datasource half a dozen of times before making the mistake of requesting an out-of-bounds element (thus, the tableview should be well aware of the new count). Currently, every modification of the datasource is immediately followed by a reloadData request to the tableView. Is there a way to ensure that these two consecutive calls are executed without the processor switching to an other thread ? Cheers, Jean ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: DataSource modified while TableView is displaying data
Can you post your code for numberOfRowsInTableView: Peter ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: DataSource modified while TableView is displaying data
Dear Peter, Thank you for your reply. Here is the requested code (see below). After more investigations, I would like to take back a previous statement, that the tableview was perfectly aware that the number of rows in the DS was changed. This is due to my inexperience in stepping through the code across multiple threads. Here are the facts : - Only the controller removes rows from the datasource. Either in response to user actions (canceling tasks) or as a result of a Task completed notification from a running task. - Each change in the dataSource is immediately followed by a reloadData message sent to the TableView. - Sometimes, randomly, the TableView requests a row index that is out of bounds by one index (e.g. row #5 when the dataSource contains only 5 elements). I am thinking that a redraw occurs in-between the removal of one element of the table and the reloadData message. For now, I did a quick fix for this issue by adding a category with "safeObjectAtIndex"…. But one way to be certain would be to ensure that the two messages (removal + reloadData) are sent without any thread/process switching occurring… The code for numberOfRowsInTableView : (experimentList is an instance of NSMutableAray) - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView { return self.experimentList ? [self.experimentList count] : 0; } Jean On 26 avr. 2012, at 11:45, Peter Hudson wrote: > > Can you post your code for numberOfRowsInTableView: > > Peter ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: DataSource modified while TableView is displaying data
I've had a similar problem to this in the past. I never bottomed it out. The workaround I used was to check validity of the row index for every data call and, if the index was invalid, return a suitable easily visible debug string ( "bad index") and post a timed update 0.25 second later. I never saw my debug string and the code ran happily. Peter On 26 Apr 2012, at 11:23, Jean Suisse wrote: > Dear Peter, > > Thank you for your reply. Here is the requested code (see below). > After more investigations, I would like to take back a previous statement, > that the tableview was perfectly aware that the number of rows in the DS was > changed. This is due to my inexperience in stepping through the code across > multiple threads. > > Here are the facts : > > - Only the controller removes rows from the datasource. Either in response to > user actions (canceling tasks) or as a result of a Task completed > notification from a running task. > - Each change in the dataSource is immediately followed by a reloadData > message sent to the TableView. > - Sometimes, randomly, the TableView requests a row index that is out of > bounds by one index (e.g. row #5 when the dataSource contains only 5 > elements). > > I am thinking that a redraw occurs in-between the removal of one element of > the table and the reloadData message. > For now, I did a quick fix for this issue by adding a category with > "safeObjectAtIndex"…. But one way to be certain would be to ensure that the > two messages (removal + reloadData) are sent without any thread/process > switching occurring… > > The code for numberOfRowsInTableView : (experimentList is an instance of > NSMutableAray) > > - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView > { >return self.experimentList ? [self.experimentList count] : 0; > } > > Jean > > > On 26 avr. 2012, at 11:45, Peter Hudson wrote: > >> >> Can you post your code for numberOfRowsInTableView: >> >> Peter > ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: DataSource modified while TableView is displaying data
how about using @synchronize to avoid it On Apr 26, 2012, at 6:23 PM, Jean Suisse wrote: > Dear Peter, > > Thank you for your reply. Here is the requested code (see below). > After more investigations, I would like to take back a previous statement, > that the tableview was perfectly aware that the number of rows in the DS was > changed. This is due to my inexperience in stepping through the code across > multiple threads. > > Here are the facts : > > - Only the controller removes rows from the datasource. Either in response to > user actions (canceling tasks) or as a result of a Task completed > notification from a running task. > - Each change in the dataSource is immediately followed by a reloadData > message sent to the TableView. > - Sometimes, randomly, the TableView requests a row index that is out of > bounds by one index (e.g. row #5 when the dataSource contains only 5 > elements). > > I am thinking that a redraw occurs in-between the removal of one element of > the table and the reloadData message. > For now, I did a quick fix for this issue by adding a category with > "safeObjectAtIndex"…. But one way to be certain would be to ensure that the > two messages (removal + reloadData) are sent without any thread/process > switching occurring… > > The code for numberOfRowsInTableView : (experimentList is an instance of > NSMutableAray) > > - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView > { > return self.experimentList ? [self.experimentList count] : 0; > } > > Jean > > > On 26 avr. 2012, at 11:45, Peter Hudson wrote: > >> >> Can you post your code for numberOfRowsInTableView: >> >> Peter > > > ___ > > Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) > > Please do not post admin requests or moderator comments to the list. > Contact the moderators at cocoa-dev-admins(at)lists.apple.com > > Help/Unsubscribe/Update your Subscription: > https://lists.apple.com/mailman/options/cocoa-dev/rols%40rols.org > > This email sent to r...@rols.org ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: DataSource modified while TableView is displaying data
On Apr 26, 2012, at 3:23 AM, Jean Suisse wrote: > Dear Peter, > > Thank you for your reply. Here is the requested code (see below). > After more investigations, I would like to take back a previous statement, > that the tableview was perfectly aware that the number of rows in the DS was > changed. This is due to my inexperience in stepping through the code across > multiple threads. > > Here are the facts : > > - Only the controller removes rows from the datasource. Either in response to > user actions (canceling tasks) or as a result of a Task completed > notification from a running task. > - Each change in the dataSource is immediately followed by a reloadData > message sent to the TableView. > - Sometimes, randomly, the TableView requests a row index that is out of > bounds by one index (e.g. row #5 when the dataSource contains only 5 > elements). > > I am thinking that a redraw occurs in-between the removal of one element of > the table and the reloadData message. > For now, I did a quick fix for this issue by adding a category with > "safeObjectAtIndex"…. But one way to be certain would be to ensure that the > two messages (removal + reloadData) are sent without any thread/process > switching occurring… > > The code for numberOfRowsInTableView : (experimentList is an instance of > NSMutableAray) > > - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView > { > return self.experimentList ? [self.experimentList count] : 0; > } This is fine as long as your experimentList is only ever modified on the main thread. It sounds like that isn't the case, which is why you have random problems. corbin > > Jean > > > On 26 avr. 2012, at 11:45, Peter Hudson wrote: > >> >> Can you post your code for numberOfRowsInTableView: >> >> Peter > > > ___ > > Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) > > Please do not post admin requests or moderator comments to the list. > Contact the moderators at cocoa-dev-admins(at)lists.apple.com > > Help/Unsubscribe/Update your Subscription: > https://lists.apple.com/mailman/options/cocoa-dev/corbind%40apple.com > > This email sent to corb...@apple.com ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: DataSource modified while TableView is displaying data
So if I understand you correctly alteration of the dataSource should always be performed on the main thread. [implied: because requests for redraw or similar that do not come from my code will always run on the main thread. Is that right ?]. Done. Not easy, but done. (Things were a little more complicated than just dispatching a block). Now, for the sake of knowledge, I would like to consider the case where I can't always alter the datasource on the main thread. What then ? I recall assembly instructions (STI, CLI) to prevent IRQ from occurring inside a few line of code. Is there some way to prevent thread switching for a short time ? Do you know an other way to deal with the present issue (alteration of the dataSource of an NSTableView not performed on the main thread) ? Jean On 26 avr. 2012, at 21:47, Corbin Dunn wrote: > > On Apr 26, 2012, at 3:23 AM, Jean Suisse wrote: > >> Dear Peter, >> >> Thank you for your reply. Here is the requested code (see below). >> After more investigations, I would like to take back a previous statement, >> that the tableview was perfectly aware that the number of rows in the DS was >> changed. This is due to my inexperience in stepping through the code across >> multiple threads. >> >> Here are the facts : >> >> - Only the controller removes rows from the datasource. Either in response >> to user actions (canceling tasks) or as a result of a Task completed >> notification from a running task. >> - Each change in the dataSource is immediately followed by a reloadData >> message sent to the TableView. >> - Sometimes, randomly, the TableView requests a row index that is out of >> bounds by one index (e.g. row #5 when the dataSource contains only 5 >> elements). >> >> I am thinking that a redraw occurs in-between the removal of one element of >> the table and the reloadData message. >> For now, I did a quick fix for this issue by adding a category with >> "safeObjectAtIndex"…. But one way to be certain would be to ensure that the >> two messages (removal + reloadData) are sent without any thread/process >> switching occurring… >> >> The code for numberOfRowsInTableView : (experimentList is an instance of >> NSMutableAray) >> >> - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView >> { >> return self.experimentList ? [self.experimentList count] : 0; >> } > > This is fine as long as your experimentList is only ever modified on the main > thread. > > It sounds like that isn't the case, which is why you have random problems. > > corbin > > >> >> Jean >> >> >> On 26 avr. 2012, at 11:45, Peter Hudson wrote: >> >>> >>> Can you post your code for numberOfRowsInTableView: >>> >>> Peter >> >> >> ___ >> >> Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) >> >> Please do not post admin requests or moderator comments to the list. >> Contact the moderators at cocoa-dev-admins(at)lists.apple.com >> >> Help/Unsubscribe/Update your Subscription: >> https://lists.apple.com/mailman/options/cocoa-dev/corbind%40apple.com >> >> This email sent to corb...@apple.com > ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: DataSource modified while TableView is displaying data
Not really no, not that I can think of, nothing simple at least. That's one issue with Cocoa's drawing code being main-thread only and with things like NSTableView being totally tightly bound to their datasource and delegate and liable to call them at any time (drawing, scrolling, just feeling like it), and calling more than one method in a sequence (not synchronized across the entire method call), using them cross-thread is tricky indeed. I will be interested to see if there are some simple patterns people have found to work, I haven't. In the one case I did this I batched my updates into sets of adds, deletes and changes and then blocked (or at that time performSelector'ed) them onto the main thread where they were applied to the data model there. I can certainly see cases in which that's hard to do and you want to adjust the data model on your thread and then just say redraw. On Apr 27, 2012, at 3:42 PM, Jean Suisse wrote: > So if I understand you correctly alteration of the dataSource should always > be performed on the main thread. > [implied: because requests for redraw or similar that do not come from my > code will always run on the main thread. Is that right ?]. > > Done. Not easy, but done. (Things were a little more complicated than just > dispatching a block). > > Now, for the sake of knowledge, I would like to consider the case where I > can't always alter the datasource on the main thread. What then ? > I recall assembly instructions (STI, CLI) to prevent IRQ from occurring > inside a few line of code. > Is there some way to prevent thread switching for a short time ? Do you know > an other way to deal with the present issue (alteration of the dataSource of > an NSTableView not performed on the main thread) ? > > Jean > > On 26 avr. 2012, at 21:47, Corbin Dunn wrote: > >> >> On Apr 26, 2012, at 3:23 AM, Jean Suisse wrote: >> >>> Dear Peter, >>> >>> Thank you for your reply. Here is the requested code (see below). >>> After more investigations, I would like to take back a previous statement, >>> that the tableview was perfectly aware that the number of rows in the DS >>> was changed. This is due to my inexperience in stepping through the code >>> across multiple threads. >>> >>> Here are the facts : >>> >>> - Only the controller removes rows from the datasource. Either in response >>> to user actions (canceling tasks) or as a result of a Task completed >>> notification from a running task. >>> - Each change in the dataSource is immediately followed by a reloadData >>> message sent to the TableView. >>> - Sometimes, randomly, the TableView requests a row index that is out of >>> bounds by one index (e.g. row #5 when the dataSource contains only 5 >>> elements). >>> >>> I am thinking that a redraw occurs in-between the removal of one element of >>> the table and the reloadData message. >>> For now, I did a quick fix for this issue by adding a category with >>> "safeObjectAtIndex"…. But one way to be certain would be to ensure that the >>> two messages (removal + reloadData) are sent without any thread/process >>> switching occurring… >>> >>> The code for numberOfRowsInTableView : (experimentList is an instance of >>> NSMutableAray) >>> >>> - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView >>> { >>> return self.experimentList ? [self.experimentList count] : 0; >>> } >> >> This is fine as long as your experimentList is only ever modified on the >> main thread. >> >> It sounds like that isn't the case, which is why you have random problems. >> >> corbin >> >> >>> >>> Jean >>> >>> >>> On 26 avr. 2012, at 11:45, Peter Hudson wrote: >>> Can you post your code for numberOfRowsInTableView: Peter >>> >>> >>> ___ >>> >>> Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) >>> >>> Please do not post admin requests or moderator comments to the list. >>> Contact the moderators at cocoa-dev-admins(at)lists.apple.com >>> >>> Help/Unsubscribe/Update your Subscription: >>> https://lists.apple.com/mailman/options/cocoa-dev/corbind%40apple.com >>> >>> This email sent to corb...@apple.com >> > > > ___ > > Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) > > Please do not post admin requests or moderator comments to the list. > Contact the moderators at cocoa-dev-admins(at)lists.apple.com > > Help/Unsubscribe/Update your Subscription: > https://lists.apple.com/mailman/options/cocoa-dev/rols%40rols.org > > This email sent to r...@rols.org ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch.
Re: DataSource modified while TableView is displaying data
On Apr 27, 2012, at 1:42 AM, Jean Suisse wrote: > Now, for the sake of knowledge, I would like to consider the case where I > can't always alter the datasource on the main thread. Well, if you really think about, there simply is no such case, ever. You can always post notifications of some sort to the main thread and alter the data source there. -- Scott Ribe scott_r...@elevated-dev.com http://www.elevated-dev.com/ (303) 722-0567 voice ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com
Re: DataSource modified while TableView is displaying data
Dear Roland, Well, here is my pattern: Dispatching the datasource modification / tableview reloadData code on the main thread. Waiting for it to be executed. And avoiding deadlocks by querying [NSThread isMainThread] and taking the appropriate action. This pattern works perfectly in my context, but requires more refinement if several threads have access to the controller. Context: The controller is mostly called from a serial queue, sometimes called from the main thread. Code [controller code]: if ([NSThread isMainThread]) { MODIFY DATASOURCE RELOAD DATA } else { self.continueFlag = NO; dispatch_async( dispatch_get_main_queue(), [[^{ MODIFY DATASOURCE RELOAD DATA self.continueFlag = YES; } copy] autorelease]); __WAIT_FOR(self.continueFlag, 1000); } REMARKS: __WAIT_FOR is just a polling macro, wrapped around usleep(); periodically testing for a condition to become true before continuing (here: polling time = 1000 µs). The copy/autorelease may not be mandatory. Jean Institut de Chimie Moléculaire de l’Université de Bourgogne (ICMUB) — UMR 6302 U.F.R. Sciences et Techniques, Bâtiment Mirande Aile B, bureau 411 9, avenue Alain Savary — B.P. 47870 21078 DIJON CEDEX T: +333-8039-9037 F: +339-7223-9232 E: jean.sui...@u-bourgogne.fr http://www.icmub.fr/185-JEAN-SUISSE_?id=331 On 27 avr. 2012, at 12:42, Roland King wrote: > Not really no, not that I can think of, nothing simple at least. > > That's one issue with Cocoa's drawing code being main-thread only and with > things like NSTableView being totally tightly bound to their datasource and > delegate and liable to call them at any time (drawing, scrolling, just > feeling like it), and calling more than one method in a sequence (not > synchronized across the entire method call), using them cross-thread is > tricky indeed. > > I will be interested to see if there are some simple patterns people have > found to work, I haven't. In the one case I did this I batched my updates > into sets of adds, deletes and changes and then blocked (or at that time > performSelector'ed) them onto the main thread where they were applied to the > data model there. I can certainly see cases in which that's hard to do and > you want to adjust the data model on your thread and then just say redraw. > > On Apr 27, 2012, at 3:42 PM, Jean Suisse wrote: > >> So if I understand you correctly alteration of the dataSource should always >> be performed on the main thread. >> [implied: because requests for redraw or similar that do not come from my >> code will always run on the main thread. Is that right ?]. >> >> Done. Not easy, but done. (Things were a little more complicated than just >> dispatching a block). >> >> Now, for the sake of knowledge, I would like to consider the case where I >> can't always alter the datasource on the main thread. What then ? >> I recall assembly instructions (STI, CLI) to prevent IRQ from occurring >> inside a few line of code. >> Is there some way to prevent thread switching for a short time ? Do you know >> an other way to deal with the present issue (alteration of the dataSource of >> an NSTableView not performed on the main thread) ? >> >> Jean >> >> On 26 avr. 2012, at 21:47, Corbin Dunn wrote: >> >>> >>> On Apr 26, 2012, at 3:23 AM, Jean Suisse wrote: >>> Dear Peter, Thank you for your reply. Here is the requested code (see below). After more investigations, I would like to take back a previous statement, that the tableview was perfectly aware that the number of rows in the DS was changed. This is due to my inexperience in stepping through the code across multiple threads. Here are the facts : - Only the controller removes rows from the datasource. Either in response to user actions (canceling tasks) or as a result of a Task completed notification from a running task. - Each change in the dataSource is immediately followed by a reloadData message sent to the TableView. - Sometimes, randomly, the TableView requests a row index that is out of bounds by one index (e.g. row #5 when the dataSource contains only 5 elements). I am thinking that a redraw occurs in-between the removal of one element of the table and the reloadData message. For now, I did a quick fix for this issue by adding a category with "safeObjectAtIndex"…. But one way to be certain would be to ensure that the two messages (removal + reloadData) are sent without any thread/process switching occurring… The code for numberOfRowsInTableView : (experimentList is an instance of NSMutableAray) - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView { return self.experimentList ? [self.experimentList count] : 0; } >>> >>> This is fine as long as your
Re: DataSource modified while TableView is displaying data
On Apr 27, 2012, at 7:19 AM, Jean Suisse wrote: > Dispatching the datasource modification / tableview reloadData code on the > main thread. Waiting for it to be executed. And avoiding deadlocks by > querying [NSThread isMainThread] and taking the appropriate action. This > pattern works perfectly in my context, but requires more refinement if > several threads have access to the controller. It would be much simpler to post updates to the main thread, and let them be run on their own schedule. Then you have no need for synchronization, no worries about deadlock, no busy-polling loop... -- Scott Ribe scott_r...@elevated-dev.com http://www.elevated-dev.com/ (303) 722-0567 voice ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to arch...@mail-archive.com