Fairly simple threading question

Discussion in 'Mac Programming' started by cazlar, Jun 19, 2007.

  1. cazlar macrumors 6502

    Joined:
    Oct 2, 2003
    Location:
    Sydney, Australia
    #1
    I have a quick question regarding threading, with probably a real basic answer that I've managed to overlook.

    I've got a document based app which spins off a thread. If I close the document while the thread is running, it will crash the app the next time it tries to access any of the (now dealloc'ed) elements of that document (specifically NSArraycontrollers). I thought I could keep a reference to the thread and kill it, but it seems you can only kill it from within the thread?

    How do I kill the thread externally, or otherwise is there a way to easily check if those IBObjects it is trying to hit actually still exist (and if they don't, I can then exit the thread from within).
     
  2. MongoTheGeek macrumors 68040

    MongoTheGeek

    Joined:
    Sep 13, 2003
    Location:
    Its not so much where you are as when you are.
    #2
  3. Nutter macrumors 6502

    Joined:
    Mar 31, 2005
    Location:
    London, England
    #3
    You should always retain objects before passing them to another thread. Unless you're passing objects that will be around for the whole life of the application, not retaining before passing something to the second thread is a bad idea.

    Note that if you're using +[NSThread detachNewThreadSelector:toTarget:withObject:], the target and object arguments are retained for you throughout the life of the thread.

    I'm not sure if that will help you ... perhaps you could give a few more details about what your second thread is doing?
     
  4. cazlar thread starter macrumors 6502

    Joined:
    Oct 2, 2003
    Location:
    Sydney, Australia
    #4
    Thanks for the suggestions, I'll try them today. Here's a bit more info in case it helps:

    The thread is basically a file import routine I have in my MyDocument.m, separate from the normal open file routine. It is called from within this class. It's reading in and parsing ~35MB of text (~100k records), so may take a while, which was why I put it in another thread, to prevent the whole app blocking during that time. The only info I'm passing to the thread is the path to the file to import. Once the thread does it's work, it attempts to then inject the data it has parsed out into the appropriate NSArrayController (and thus into my model). The NSArrayController is from the NIB, and is an IBOutlet in MyDocument.h.

    Where the problem is, is that closing the document kills the document's window and the objects in the NIB etc as you would expect. I was sort of expecting that the thread spun off from that particular NSDocument instance would also be killed at that time too, but it of course lives on, and causes the crash when it finally finishes and tries to access the IBOutlet.

    I'll try retaining it as you both suggest, but what would be best would some way to detect that the document is still there (actually, maybe I can check on the status of the window?), as this would allow me to kill the thread during my loop, rather than let it finish, as while that may not crash, it's uselessly churning the CPU for no reason at all.
     
  5. Krevnik macrumors 68030

    Krevnik

    Joined:
    Sep 8, 2003
    #5
    You have a few things you can do here... one is the easy way, one is the 'right' way (IMO).

    The easy way is to catch the exception as suggested. The 'try' block should encapsulate your thread loop, and so when it catches, it will jump out of your loop and let you exit semi-gracefully. But if you can make the try block as small as possible and return from the catch block, that is preferable. However, this might not catch all exceptions, so it will probably work, but there is a chance it won't.

    The problem I see here is your thread is trying to manipulate your UI directly, but it is a data producer, not a data consumer. It is risky and usually bad form to try to access UI elements across thread boundaries, and makes code harder to maintain in the future. I personally (at best) would pass it the path and NSMutableArray that the data will be going into. A thread should be as light-weight as possible, so try to avoid giving it direct control over objects it doesn't need to be touching. If you use CoreData, give it access to the Managed Object Context, which should be thread safe. Otherwise, give it access to the NSMutableArray driving your array controller. This isn't because it doesn't work, but rather it makes threading easier to manage.

    Last and probably least, there should be some messaging mechanism to let you signal your thread to die. This will let you synchronize your thread death with the death of the document/app correctly. Usually this is done with some sort of code similar to this psudeocode:

    Code:
    signal_my_thread_to_die();
    join_thread(thread);
    
    In both NSThread, and pthread, there is a call that lets you 'join' the thread. What this means is that your main thread will block until your other thread is dead. This lets you cleanly exit the thread, which could include cleanup, and then clean up the document before it is completely dealloc'd.
     
  6. cazlar thread starter macrumors 6502

    Joined:
    Oct 2, 2003
    Location:
    Sydney, Australia
    #6
    Thanks again for all the help, I'll definitely look to try separate out the UI stuff if possible, and add more exception catching (I'm terrible with stuff like that - I tend to write "optimistic" programs!).

    In the meantime, I've managed to temporarily hack around it by using a few BOOLs (wantsToClose and canClose) to halt the main thread in windowWillClose: (before anything is destroyed) until the subthread checks back on wantsToClose, and if it sees that it does want to close, tells the main thread "yep, now you can close", then happily kills itself. This seems to work well (but may not be great form!).
     
  7. garethlewis2 macrumors 6502

    Joined:
    Dec 6, 2006
    #7
    You've fallen into the trap that most developers get into with Cocoa. Oh no, I have to process a gigantic amount of data and I don't want the UI getting stuck when the user presses the go button. Simple, I'll use a thread. No. Don't use a thread unless you have some work that needs to run in the background permantently, or you are splitting the processing of the file into several chunks and spawn several threads processing a seperate part of the file. If no is the answer to any of those, you should be using an NSTimer object.
     
  8. Nutter macrumors 6502

    Joined:
    Mar 31, 2005
    Location:
    London, England
    #8
    Also, your optimistic programming style isn't going to help you here! Catching exceptions won't fix this problem. If you're using threads you really must make sure that they aren't accessing shared data unless: 1) The data is totally immutable and has been since before you spun off the thread, or 2) You use some kind of lock when reading and writing.

    Accessing an NSArrayController from both threads is a very bad idea. Even using shared BOOLs isn't safe.

    Your thread could use -[NSObject performSelectorOnMainThread:withObject:waitUntilDone:] to pass the extracted data to an object on your main thread. Your main thread can then safely inject it into the NSArrayController, or discard it if the document has been closed.

    But as Gareth says, if you can use a timer, do.
     
  9. indiekiduk macrumors 6502

    Joined:
    Jul 26, 2005
    Location:
    Glasgow, Scotland
    #9
    Obviously we are not using an NSTimer because we do not want to hang the UI at all, even for one second.

    This is what I am doing, but I would like to know how to check if the document is closed? I don't see an isClosed function in MyDocument. I guess I need my own bool for that set in windowWillClose
     
  10. Catfish_Man macrumors 68030

    Catfish_Man

    Joined:
    Sep 13, 2001
    Location:
    Portland, OR
    #10
    <edit> Took too long writing this. Oh well. </edit>

    AppKit as a whole (and as part of it, nib files) are generally not threadsafe. I would recommend making a single NSMutableArray that's shared between the data processing thread and the main thread.

    The tricky bit of that is that you have to figure out how to update the UI then, and polling is generally considered bad form. I would probably use NSObject's -performSelectorOnMainThread: method for this.

    Something like this (sorta pseudocode):
    Code:
    -(void)backgroundProcessData:(NSURL *)file
    {
        //assume the usual autorelease pool, etc...
        while(!done)
        {
             /*If you want to update the UI periodically while this is running
              *you'll need an @synchronized(self) block around it, or 
              *you'll need to use waitUntilDone:YES
              */
             [tempStorage addObject:somethingorother];
        }
        [self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
    }
    
    - (void) updateUI
    {
         if(documentIsDead)
               return;
         //see note above about synchronization
         [arrayController setContent:tempStorage];
    }
    
    
    <edit 2> Actually if you don't care about progress updates, you can make tempStorage a local of the processing function and just pass it as the argument in performSelectoOnMainThread :) </edit 2>
     
  11. Mac Player macrumors regular

    Joined:
    Jan 19, 2006
    #11
    What if each document window had its own thread?
     
  12. Catfish_Man macrumors 68030

    Catfish_Man

    Joined:
    Sep 13, 2001
    Location:
    Portland, OR
    #12
    The suggestion I posted would work fine for that (although the document UI for all of them would still be on the main thread).
     

Share This Page