From: Robert Nicholson <email@hidden>
Date: June 3, 2007 11:50:03 PM GMT+07:00
To: Cocoa Developers Developers <email@hidden>
Subject: Blocking a method's return until an "async' forked thread
has finished it's work.
In general this is allowed right?
ie. if I have some methods that will update the condition lock I
can initialize with the total number of workers and as each worker
finishes or errors I can deduct by 1 the value of the condition
count until it's reaches 0 and then the method will unblock and
return right?
In this case the runJobs: method forks again and the same approach
is used to block until an async "process" is complete.
The machport is used to return the result of the work for each
item back to the parent thread.
- (void)runJobsInMultipleThreads:(unsigned int)count
{
int i;
//initialize the threadLock with the number of thread
[threadLock lock];
[threadLock unlockWithCondition:count];
NSPort *mainPort = [NSMachPort port];
if (!mainPort)
{
return;
}
[mainPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:mainPort
forMode:NSDefaultRunLoopMode];
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
[parameters setObject:mainPort forKey:@"port"];
// start the secondary threads
// loop starts at 1 because the main thread is also used (see
below)
for (i=0;i<count;i++) {
[NSThread detachNewThreadSelector:@selector(runJobs:)
toTarget:self
withObject:parameters];
}
//wait until done, ie until the count of threads in threadLock
is down to 0
[threadLock lockWhenCondition:0];
[threadLock unlock];
}
so at the runJobs: level it looks like this
- (void)runJobs:(NSMutableDictionary *)parameters;
{
NSAutoreleasePool *pool;
BCKJob *aJob;
//run jobs from the queue until none left
pool=[[NSAutoreleasePool alloc] init];
NSMachPort *port = [parameters objectForKey:@"port"];
aJob = [queue tryDequeue];
do {
if (aJob) {
[aJob setPort:port];
[aJob runJob];
}
} while (aJob = [queue tryDequeue]);
//the thread is done with the jobs
//decrease the count of threads in threadLock
[threadLock lock];
[threadLock unlockWithCondition: ([threadLock condition] - 1) ];
[pool release];
pool = nil;
}
and a call to runJob forks again for each item in a workers batch
of work.
- (void)runJob
{
int i = 0;
[batchLock lock];
[batchLock unlockWithCondition:[batch count]];
for (i=0;i<[batch count];i++) {
NSURL *url = [batch objectAtIndex:i];
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
[parameters setObject:port forKey:@"port"];
[parameters setObject:url forKey:@"url"];
[NSThread detachNewThreadSelector:@selector(doDownload:)
toTarget:self
withObject:parameters];
}
[batchLock lockWhenCondition:0];
[batchLock unlock];
}
and doDownload looks like this
- (void)doDownload:(NSMutableDictionary *)parameters
{
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [parameters objectForKey:@"url"];
NSURLRequest *r = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:timeout];
NSURLDownload *d = [[NSURLDownload alloc] initWithRequest:r
delegate:self];
double resolution = 1.0;
BOOL isRunning;
do {
NSDate *next = [NSDate
dateWithTimeIntervalSinceNow:resolution];
isRunning = [[NSRunLoop currentRunLoop]
runMode:NSDefaultRunLoopMode
beforeDate:next];
} while (isRunning && ((completedCount + errorCount) == [batch
count]));
[pool release];
pool = nil;
}
If anything I'm currently retaining more than I should but until I
solve the memory related SIGTRAP I'm not too worried about that.
so the idea is that nothing will be autorelease until all work is
complete.
completeCount and errorCount and updated in
- (void)download:(NSURLDownload *)download didFailWithError:
(NSError *)error
- (void)downloadDidFinish:(NSURLDownload *)download
as well as the batchLock which should ensure that runJob does not
return until
it's finished it's batch of work.
One particuarly annoying aspect of NSURLDownload is that you get
notified that a file is created
regardless of whether the download is successful or not. So you
have to build a map the download request against
the path in order to be able to return the path later on. I'd much
prefer that when it either error'd or finished that it also gave
you the path it's used.
I wouldn't mind seeing the source of
NSSynchronousURLConnectionDelegate just to see how it blocks the
calling thread until all the work is complete.