Re: NSOperationQueue
Re: NSOperationQueue
- Subject: Re: NSOperationQueue
- From: Mike Fischer <email@hidden>
- Date: Sat, 20 Sep 2008 02:57:13 +0200
Am 20.09.2008 um 00:51 schrieb John Love <email@hidden>:
No sooner do I say "Solved" that I determine "Not Solved".
I am no longer crashing in part due to the fact that I've removed
NSApplescript from the thread; however, the evidence is still
insurmountable that the calculation is not running in a background
Thread, but in the main thread. Because it's in the foreground, I
lose control of my application until the Thread finishes.
I'm betting it is not running at all, see below.
If you have already answered my problem, I apologize in advance for
the noise.
Some of this repeats what I've already posted, but I am going big-time
overboard in an attempt to be complete:
By way of background:
1) a IBOutlet of MyDocument = MyDocController
2) a IBOutlet of MyDocController = MyCalculationController
To continue:
3) In MyDocument's windowControllerDidLoadNib I call MyDocController's
MakeNewFile to which I pass the (NSURL*)theFile which is the file I
just double-clicked on Cocoa's built-in open dialog. (If theFile =
nil) then I open up a blank document) If theFile is not nil, then I
call MyDocController's startCalculation.
4) MyDocController's startCalculation calls MyCalculationController's
startCalculation.
Now, the threading "fun" begins all within MyCalculationController:
6) Its Interface looks like:
@interface MyCalculationController:NSObject {
NSOperationQueue *itsQueue;
NSInvocationOperation *itsOp;
It seems kind of pointless to allow only a single operation for the
queue.
int itsCalcStatus;
}
7) Its -init looks like:
- (id) init {
if (self = [super init]) {
itsCalcStatus = kNoError; // an enumerated constant
itsQueue = [[NSOperationQueue alloc] init];
itsOp = nil;
}
return self;
}
8) Its -dealloc looks like:
- (void) dealloc {
[itsQueue release];
// [itsOp release]; // each Operation released after it is finished
[super dealloc];
}
9) Its -startCalculation looks like:
- (void) startCalculation {
int row;
// itsCalcStatus = kNoError; // set by -init
for (row=1; row < 10000; row++) {
if (itsCalcStatus == kSpreadsheetStopped) break;
if (itsCalcStatus != kNoError) break;
itsCalcStatus = kSpreadsheetCalculating;
[self startOperation]; // start new thread
// if running in background, this will have no effect:
[itsQueue waitUntilAllOperationsAreFinished];
See the documentation:
"waitUntilAllOperationsAreFinished
Blocks the current thread until all of the receiver’s queued and
executing operations finish executing."
<http://developer.apple.com/documentation/Cocoa/Reference/
NSOperationQueue_class/Reference/Reference.html#//apple_ref/occ/instm/
NSOperationQueue/waitUntilAllOperationsAreFinished>
So if -startCalculation is executed on the main thread (which seems
to be the case AFAICT) then the main thread will block until the
operation is done. In that case the whole concept of using
NSOperation becomes just so much overhead without any practical benefit.
I would expect you to queue an NSOperation in the loop and if you
really need to wait until all of them are done executing then you
could wait after the loop. Note though, that you would still be
blocking the main thread. But at least you would be taking advantage
of NSOperationQueues ability to schedule multiple operations in
parallel depending on available system resources.
But maybe even that will not be necessary. If it isn't then you need
to restructure your code to continue after some sort of notification
that all of your operations are done. You could keep a counter which
is initialized to the number of rows and decremented whenever an
operation is done. Make sure to access this counter only with proper
locks in place (see @synchronized() etc.). When the counter reaches 0
then fire a notification to your main thread or trigger the next step
in your workflow by calling one of the performSelectorOnMainThread:
methods. Or in keeping with the NSOperation model you could fire off
a special NSOperation which "watches" your counter periodically until
the counter reaches 0 and have the rest of your workflow in another
NSOperation that is dependent on this operation.
BTW: I don't see how you are communicating what the (background)
operation is actually supposed to do. The local variable row is not
accessible to the code running in the NSOperation.
}
// After the for-loop completes, itsCalcStatus =
// kSpreadsheetCalculating, kSpreadsheetStopped, kNoExcelApp, or
kNoWorkbook
// So ...
if (itsCalcStatus == kSpreadsheetCalculating) {
// no errors
[self finishCalculation];
}
else {
// either stopped or an un-recoverable error = kNoExcelApp,
kNoWorkbook
[self stopOperation];
// this MyDocument is done and the only user option is to close this
doc
// and open a new doc, beginning with a fresh Queue.
[itsQueue release];
}
}
10) The methods called within -startCalculation look like:
- (void) startOperation {
itsOp = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(calculateWorksheetRow:)
This is the wrong selector signature. Your method is defined below to
not take a parameter so its selector signature does not end with a
colon. Use @selector(calculateWorksheetRow) or redefine the method.
As it stands your method would never be called.
object:nil];
[itsQueue addOperation:itsOp];
Adding the object to the queue should make the queue responsible for
keeping it around (i.e. retaining and releasing it). So you can
probably release it right here. That would avoid needing to have
access to the operation object in -finishOperation and partly in -
stopOperation.
}
- (void) finishOperation {
[itsOp release]; // -calculateWorksheetRow completely finished
Not needed, see above.
}
- (void) stopOperation {
[itsOp cancel];
OK, cancel does not really stop the operation. It only sets a flag
which the code can check using -isCancelled. In your case you could
either use [itsQueue cancelAllOperations] or, if you have other types
of operations in the same queue set a flag yourself that can be
checked. (Watch out for proper synchronization!)
Either way you would not need itsOp at this point.
[itsOp release];
Not needed, see above.
}
11) ultimately called from MyDocument's -(IBAction) stopDocument:
(id)sender:
- (void) stopCalculation {
itsCalcStatus = kSpreadsheetStopped; // -startCalculation breaks
from its for-loop
That would only work if -startCalculation was running on a second
thread while the UI is actually running. AFAICT that is not the case
or am I missing something?
In any case accessing the same variable from multiple threads without
synchronization is dangerous! All sorts of weird things might happen
even if this is probably one of the more harmless cases.
}
12) Finally, my -calculateWorksheetRow looks like:
- (void) calculateWorksheetRow {
// column after column after column ...
I didn't follow this complete discussion thread but I kind of read
between the lines that your operation is actually calling a different
process (Microsoft Excel) using AppleScript? If so you will need to
rethink your strategy I think. To my knowledge AppleScript is not
thread safe (from the documentation of NSAppleScript: "Important:
You should access NSAppleScript only from the main thread.") and
running multiple AppleScripts targeting the same app in parallel
(which would be the reason for using an NSOperationQueue in the first
place) will at best not gain you any advantage and at worst crash
yourself and/or Excel.
[self finishOperation];
}
Again, if this is too wordy, I really apoligize for all the noise.
John Love
HTH
Mike
--
Mike Fischer Softwareentwicklung, EDV-Beratung
Schulung, Vertrieb
Note: I read this list in digest mode!
Send me a private copy for faster responses.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
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:
This email sent to email@hidden