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.


