Pausing and terminating a thread

  • I have implemented pausing and terminating in a worker thread
    (NSThread) by polling a state variable updated by the main thread and
    acting accordingly. This works fine, but is there a way to make the
    worker thread pause/continue or exit from my main thread so I don't
    have to do the polling?

    --
    Watch me learn Cocoa  http://homepage.mac.com/bagelturf/
  • On 30 aug 2005, at 08.08, Steve Weller wrote:

    > I have implemented pausing and terminating in a worker thread
    > (NSThread) by polling a state variable updated by the main thread
    > and acting accordingly. This works fine, but is there a way to make
    > the worker thread pause/continue or exit from my main thread so I
    > don't have to do the polling?

    NSConditionLock?

    j o a r
  • So I can use a lock to implement the pause -- neat. Just acquire the
    lock in my main thread and that will stop the worker thread from
    continuing until I release it.

    However this still requires that my worker thread poll. And doesn't
    take care of quitting the worker thread. Any better way?

    On Aug 29, 2005, at 11:14 PM, j o a r wrote:

    >
    > On 30 aug 2005, at 08.08, Steve Weller wrote:
    >
    >
    >> I have implemented pausing and terminating in a worker thread
    >> (NSThread) by polling a state variable updated by the main thread
    >> and acting accordingly. This works fine, but is there a way to
    >> make the worker thread pause/continue or exit from my main thread
    >> so I don't have to do the polling?
    >>
    >
    > NSConditionLock?
    >
    > j o a r
    >
    >
    >
    >

    --
    Watch me learn Cocoa  http://homepage.mac.com/bagelturf/
  • On 30 aug 2005, at 08.23, Steve Weller wrote:

    > However this still requires that my worker thread poll. And doesn't
    > take care of quitting the worker thread. Any better way?

    If you're waiting for a condition lock - sleeping the thread while
    doing so - you're not polling. You're essentially event driven, just
    like you would want to be.
    As for exiting the thread, just make sure that the thread under which
    conditions it is allowed to live / die, and let it make that decision
    for itself once you wake it up.

    j o a r
  • I think to end the thread you still need some sort of "poll". I've
    written a threaded object to download records in the background; it
    "schedules" units of work through the run loop using performSelector:
    and has a timer. The timer has a very short duration -- it counts
    expirations before killing things off -- and takes care of the thread
    exit by invalidating itself when there's no work and the timer expires.

    The timer method also checks an ivar "stop" which can be set by the
    main thread; if true, it schedules calling a connectionWasCancelled
    delegate method on the main thread before ending the worker thread.
    Since I need a timer in the background thread anyway, it ends up
    "polling."

    Which brings me to the real reason I'm writing this. Having looked at
    your web page (nice, BTW), your thread should likely run in some
    object which calls delegate methods (which you'll have to define) on
    your document or app controller. Why? Because they should be called
    with performSelectorOnMainThread: in order to allow your main object
    (s) to update the GUI safely. Not doing this may be a cause for the
    weird behavior you're seeing. I would include in the work that should
    only be done on the main thread not only explicit GUI updates like
    updating a progress bar, but indirect updates through bindings.

    You might have a couple of delegates, didGenerateRange and didFinish,
    so that your main object can release the threaded object once its
    done. I will post some example code (ugly but functional) on my web
    site (which, I have to say, you inspired -- I even ended up using the
    same them in RapidWeaver ;-).

    Matt Holiday -- http://homepage.mac.com/matthol2/cocoa/page1/page1.html

    > Message: 13
    > Date: Mon, 29 Aug 2005 23:23:08 -0700
    > From: Steve Weller <bagelturf...>
    > Subject: Re: Pausing and terminating a thread
    >
    > So I can use a lock to implement the pause -- neat. Just acquire the
    > lock in my main thread and that will stop the worker thread from
    > continuing until I release it.
    >
    > However this still requires that my worker thread poll. And doesn't
    > take care of quitting the worker thread. Any better way?
    >
    > On Aug 29, 2005, at 11:14 PM, j o a r wrote:
    >
    >> On 30 aug 2005, at 08.08, Steve Weller wrote:
    >>
    >>> I have implemented pausing and terminating in a worker thread
    >>> (NSThread) by polling a state variable updated by the main thread
    >>> and acting accordingly. This works fine, but is there a way to
    >>> make the worker thread pause/continue or exit from my main thread
    >>> so I don't have to do the polling?
    >>
    >> NSConditionLock?
  • On Mon, 29 Aug 2005 23:23:08 -0700, Steve Weller <bagelturf...>
    wrote:

    > So I can use a lock to implement the pause -- neat. Just acquire the
    > lock in my main thread and that will stop the worker thread from
    > continuing until I release it.
    >
    > However this still requires that my worker thread poll. And doesn't
    > take care of quitting the worker thread. Any better way?

    It's not clear what you would like to be able to do. Do you mean you
    want to be able to pause and terminate your worker thread without
    performing any tests within its main loop?

    Is there not communication between worker and GUI anyway, to inform
    of progress?

    Best wishes,
    Hamish
  • On Aug 30, 2005, at 10:16 AM, Hamish Allan wrote:

    > On Mon, 29 Aug 2005 23:23:08 -0700, Steve Weller
    > <bagelturf...> wrote:
    >
    >> So I can use a lock to implement the pause -- neat...
    >>
    >> However this still requires that my worker thread poll. And doesn't
    >> take care of quitting the worker thread. Any better way?
    >>
    >
    > It's not clear what you would like to be able to do. Do you mean
    > you want to be able to pause and terminate your worker thread
    > without performing any tests within its main loop?
    >
    > Is there not communication between worker and GUI anyway, to inform
    > of progress?

      I've run into the same question.  You want a background thread to
    do a whole lot of work as fast as possible.  Due to user actions or
    whatever, though, you may want to pause or terminate the background
    thread at some unpredictable time in the future.  As far as I was
    able to figure out, the background thread must poll to determine if
    it is supposed to pause or terminate.
      And as an aside: you might think this would be fairly irrelevant;
    how long does it take to check a flag every 1/10th of a second, for
    example?  But the problem is the logic that figures out "I've been
    working long enough that it's time to check my flag".  That logic
    would have to run every time though the loop of whatever your thread
    is doing, of course, which is a silly waste of time; so you end up
    having to just check the flag every time through your loop, as that
    is faster than trying to figure out if it's time to check your flag
    yet.  If your work loop is doing easy, fast stuff, you end up
    checking the flag a *lot*, and it slows down your work
    significantly.  Unrolling your work loop can help somewhat, but of
    course you don't want to do that too much or you'll start causing
    cache problems because your code is too big, plus of course unrolled
    code is hard to maintain.  So it's an annoying problem.
      It would sure be nice to have a way of pausing and killing
    background threads from the main thread without polling, is what I'm
    saying.  :->

    Ben Haller
    Stick Software
  • Here's an approach I used when implementing something similar to
    Finder's spotlight search window.  Hopefully it should give you some
    idea of how I did this.

    (Some code omitted for compactness)

    @interface SomeClass
    {
        NSLock *aLock;
        volatile BOOL stopThread
    }
    @end

    @implementation SomeClass

    - (id)init
    {
        if (self = [super init])
        {
            aLock = [[NSLock alloc] init];

        stopThread = NO;
      }

      return self;
    }

    - (void)updateQuery:(NSNotification *)aNotification
    {
        if ([queryString isEqualToString:@""])
        {
            return;
        }

        if (query)
        {
            [self stopThread];

            [lock lock];

            MDQueryStop(query);

            CFNotificationCenterRemoveObserver
    (CFNotificationCenterGetLocalCenter(), NULL,
    kMDQueryDidFinishNotification, query);
            CFNotificationCenterRemoveObserver
    (CFNotificationCenterGetLocalCenter(), NULL,
    kMDQueryProgressNotification, query);

            CFRelease(query);
            [queryResults release];
        }
        else
        {
            [self stopThread];

            [lock lock];
        }

        [self willChangeValueForKey:@"queryResults"];
        query = MDQueryCreate(kCFAllocatorDefault, (CFStringRef)
    queryString, NULL, (CFArrayRef) [NSArray arrayWithObject:(NSString *)
    kMDItemFSName]);
        CFNotificationCenterAddObserver
    (CFNotificationCenterGetLocalCenter(), NULL, &queryUpdated,
    kMDQueryProgressNotification, query,
    CFNotificationSuspensionBehaviorDrop);
        CFNotificationCenterAddObserver
    (CFNotificationCenterGetLocalCenter(), NULL, &queryFinished,
    kMDQueryDidFinishNotification, query,
    CFNotificationSuspensionBehaviorDrop);
        queryResults = [[NSMutableArray array] retain];
        MDQueryExecute(query, 0);
        [self didChangeValueForKey:@"queryResults"];

        stopThread = NO;

        [lock unlock];
    }

    - (void)updateExampleList:(NSNotification *)aNotification
    {
        //Make sure there's not another thread running
        [self stopThread];

        [lock lock];

        stopThread = NO;

        MDQueryDisableUpdates(query);
        [NSThread detachNewThreadSelector:@selector
    (updateInSeparateThread) toTarget:self withObject:nil];

        [lock unlock];
    }

    - (void)stopThread
    {
        @synchronized(self)
        {
            stopThread = YES;
        }
    }

    - (void)updateInSeparateThread
    {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        NSMutableArray *newResults;
        NSMutableDictionary *newItem;
        CFStringRef fileName, itemPath, modificationDate;
        int i;
        CFIndex count;

        [lock lock];

        if (stopThread)
        {

            [lock unlock];

            [pool release];

            return;
        }

        [self performSelectorOnMainThread:@selector
    (willChangeValueForKey:) withObject:@"queryResults" waitUntilDone:YES];

        newResults = [NSMutableArray array];

        count = MDQueryGetResultCount(query);
        for (i = 0; i < count; i++)
        {
            MDItemRef item = (MDItemRef) MDQueryGetResultAtIndex(query, i);

            newItem = [NSMutableDictionary dictionary];

            fileName = MDItemCopyAttribute(item, kMDItemFSName);
            itemPath = MDItemCopyAttribute(item, kMDItemPath);
            modificationDate = MDItemCopyAttribute(item,
    kMDItemContentModificationDate);

            [newItem setValue:((NSString *) fileName) forKey:(NSString
    *) kMDItemFSName];
            [newItem setValue:((NSString *) itemPath) forKey:(NSString
    *) kMDItemPath];
            [newItem setValue:((NSString *) modificationDate) forKey:
    (NSString *) kMDItemContentModificationDate];

            CFRelease(fileName);
            CFRelease(itemPath);
            CFRelease(modificationDate);

            [newResults addObject:newItem];
                if (stopThread)
                {

                    [lock unlock];

                    [pool release];

                    return;
                }
        }

        [lock unlock];

        [self performSelectorOnMainThread:@selector(updateList:)
    withObject:newResults waitUntilDone:YES];
        [self performSelectorOnMainThread:@selector
    (didChangeValueForKey:) withObject:@"queryResults" waitUntilDone:YES];

        [pool release];
    }

    - (void)updateList:(NSArray *)newList
    {
        [lock lock];
            [queryResults autorelease];
            queryResults = [newList retain];
            MDQueryEnableUpdates(query);
        [lock unlock];
    }

    On Aug 30, 2005, at 3:18 PM, Ben Haller wrote:

    > On Aug 30, 2005, at 10:16 AM, Hamish Allan wrote:
    >
    >
    >> On Mon, 29 Aug 2005 23:23:08 -0700, Steve Weller
    >> <bagelturf...> wrote:
    >>
    >>
    >>> So I can use a lock to implement the pause -- neat...
    >>>
    >>> However this still requires that my worker thread poll. And doesn't
    >>> take care of quitting the worker thread. Any better way?
    >>>
    >>>
    >>
    >> It's not clear what you would like to be able to do. Do you mean
    >> you want to be able to pause and terminate your worker thread
    >> without performing any tests within its main loop?
    >>
    >> Is there not communication between worker and GUI anyway, to
    >> inform of progress?
    >>
    >
    > I've run into the same question.  You want a background thread to
    > do a whole lot of work as fast as possible.  Due to user actions or
    > whatever, though, you may want to pause or terminate the background
    > thread at some unpredictable time in the future.  As far as I was
    > able to figure out, the background thread must poll to determine if
    > it is supposed to pause or terminate.
    > And as an aside: you might think this would be fairly irrelevant;
    > how long does it take to check a flag every 1/10th of a second, for
    > example?  But the problem is the logic that figures out "I've been
    > working long enough that it's time to check my flag".  That logic
    > would have to run every time though the loop of whatever your
    > thread is doing, of course, which is a silly waste of time; so you
    > end up having to just check the flag every time through your loop,
    > as that is faster than trying to figure out if it's time to check
    > your flag yet.  If your work loop is doing easy, fast stuff, you
    > end up checking the flag a *lot*, and it slows down your work
    > significantly.  Unrolling your work loop can help somewhat, but of
    > course you don't want to do that too much or you'll start causing
    > cache problems because your code is too big, plus of course
    > unrolled code is hard to maintain.  So it's an annoying problem.
    > It would sure be nice to have a way of pausing and killing
    > background threads from the main thread without polling, is what
    > I'm saying.  :->
    >
    > Ben Haller
    > Stick Software
    >
    > _______________________________________________
    > Do not post admin requests to the list. They will be ignored.
    > Cocoa-dev mailing list      (<Cocoa-dev...>)
    > Help/Unsubscribe/Update your Subscription:
    > http://lists.apple.com/mailman/options/cocoa-dev/<enigma0...>
    >
    > This email sent to <enigma0...>
    >
  • On Aug 30, 2005, at 5:22 PM, Ryan Britton wrote:

    > Here's an approach I used when implementing something similar to
    > Finder's spotlight search window.  Hopefully it should give you
    > some idea of how I did this.

      Right; if I understand your code correctly, though, you are
    polling the "stopThread" ivar every time through your loop.  If your
    loop took very little time to complete one iteration, that polling
    would end up taking a substantial amount of the total time taken by
    the thread.  The only solution to that (as far as anybody on the list
    so far seems to know) is unrolling the loop so that you only have to
    check the flag every x iterations.  That's the problem.
      Nevertheless, the code is a nice illustration of what is
    apparently the best way to do this sort of thing, given current
    APIs.  I don't mean to jump on you at all.  Thanks for posting it.

    Ben Haller
    Stick Software
  • 31 aug 2005 kl. 02.48 skrev Ben Haller:

    > On Aug 30, 2005, at 5:22 PM, Ryan Britton wrote:
    >
    >
    >> Here's an approach I used when implementing something similar to
    >> Finder's spotlight search window.  Hopefully it should give you
    >> some idea of how I did this.
    >>
    >
    > Right; if I understand your code correctly, though, you are
    > polling the "stopThread" ivar every time through your loop.  If
    > your loop took very little time to complete one iteration, that
    > polling would end up taking a substantial amount of the total time
    > taken by the thread.  The only solution to that (as far as anybody
    > on the list so far seems to know) is unrolling the loop so that you
    > only have to check the flag every x iterations.  That's the problem.
    > Nevertheless, the code is a nice illustration of what is
    > apparently the best way to do this sort of thing, given current
    > APIs.  I don't mean to jump on you at all.  Thanks for posting it.

    Is there any reason you can't use a pthread with cancel type
    PTHREAD_CANCEL_ASYNCHRONOUS and pthread_cleanup_push / pop ?

    See, e.g. "Thread Cancellation And Termination" in http://
    users.actcom.co.il/~choo/lupg/tutorials/multi-thread/multi-
    thread.html and the man page for pthread_cleanup_push.

    .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .
    .  .  .  .  .  .  .  .  .  .  .  .
    . <waterspirit...> .  . www.synapticpulse.net .
  • On Aug 30, 2005, at 6:30 PM, Pandaa wrote:

    > 31 aug 2005 kl. 02.48 skrev Ben Haller:
    >
    >> Right; if I understand your code correctly, though, you are
    >> polling the "stopThread" ivar every time through your loop.  If
    >> your loop took very little time to complete one iteration, that
    >> polling would end up taking a substantial amount of the total time
    >> taken by the thread.  The only solution to that (as far as anybody
    >> on the list so far seems to know) is unrolling the loop so that
    >> you only have to check the flag every x iterations.  That's the
    >> problem....
    >
    > Is there any reason you can't use a pthread with cancel type
    > PTHREAD_CANCEL_ASYNCHRONOUS and pthread_cleanup_push / pop ?
    >
    > See, e.g. "Thread Cancellation And Termination" in http://
    > users.actcom.co.il/~choo/lupg/tutorials/multi-thread/multi-
    > thread.html and the man page for pthread_cleanup_push.

      Hmm, interesting.  Are NSThreads guaranteed to be POSIX threads
    under the hood, or do they just happen to be at present? In any case,
    it seems they are certainly distinct in some ways.  Which I guess is
    exactly what we're talking about here.
      I'd be nervous about doing things to NSThreads (like terminating
    them) behind Cocoa's back; unless Apple has guaranteed that this is
    OK, I'd worry that it would only work on some OS releases, and might
    have weird side effects.  Has anybody tried it?
      You could certainly just use pthreads all the way through, but
    then you'd lose various NSThread niceties, and I wonder if there are
    ways in which AppKit or Foundation might not like being called from a
    background thread that was not an NSThread.
      And I only just glanced at the references you gave, just long
    enough to see they were about pthreads, not Cocoa threads, so sorry
    if I completely missed the point here.
      In any case, the limits of my knowledge have been reached, so I
    don't imagine I'll be posting on this topic again; I'd love to hear
    more if anybody has played around with pthreads and Cocoa, though...
    or if Chris Kane would like to weigh in, of course :->...

    Ben Haller
    Stick Software
  • On Aug 30, 2005, at 15:18, Ben Haller wrote:
    > If your work loop is doing easy, fast stuff, you end up checking
    > the flag a *lot*, and it slows down your work significantly.
    > Unrolling your work loop can help somewhat, but of course you don't
    > want to do that too much or you'll start causing cache problems
    > because your code is too big, plus of course unrolled code is hard
    > to maintain.  So it's an annoying problem.

    You don't have to unroll ... an approach like this works and for all
    but the most trivial work loops adds negligible overhead.

    int iteration = 0;
    while (.... doing some work ....) {

        if (++iteration == 100) {
            // update progress bar, check to see if you're done
            iteration = 0;
        }
    }

    --
    Doug Wyatt
    http://www.dougwyatt.net/  <-- music
    http://www.sonosphere.com/Doug/ <-- etc.