Any way to make dispatch_queue_t work in single thread? - ios

Here is my code:
#interface MyObject ()
#property(nonatomic) dispatch_queue_t queue;
#end
#implementation MyObject {
NSThread *_check;
}
- (id)init {
self = [super init];
if (self) {
_queue = dispatch_queue_create("com.Thread.queue", NULL);
dispatch_async(_queue, ^{
_check = [NSThread currentThread]; //for ex. thread number = 3
//some code here...
});
}
return self;
}
- (void)someMethod:(MyObjClass *)obj {
dispatch_async(_queue, ^{
//need th
if (_check != [NSThread currentThread]) { // it is sometimes number 3, but sometimes it changes
NSLog(#"Thread changed.");
}
[obj doSmth]; //got crash if currentThread != _check
});
}
#end
I need to make sure that all MyObjClass's methods performs in the same thread. But this code change thread by it's own will, but sometimes it work in single thread. Any way I can force it to use same thread all the time?

In a word, no. Other than the main queue, GCD does not have any notion of thread affinity. If you really need thread affinity, GCD is not really the right tool. If you like the idiom, and want to kind of "adapt" something to your needs you could do something like this:
#implementation AppDelegate
{
NSThread* thread;
}
void dispatch_thread_async(NSThread* thread, dispatch_block_t block)
{
if ([NSThread currentThread] == thread)
{
block();
}
else
{
block = [block copy];
[(id)block performSelector: #selector(invoke) onThread: thread withObject: nil waitUntilDone: NO];
}
}
void dispatch_thread_sync(NSThread* thread, dispatch_block_t block)
{
if ([NSThread currentThread] == thread)
{
block();
}
else
{
[(id)block performSelector: #selector(invoke) onThread: thread withObject: nil waitUntilDone: YES];
}
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
thread = [[NSThread alloc] initWithTarget: self selector:#selector(threadMain) object:nil];
[thread start];
dispatch_thread_async(thread, ^{
NSLog(#"Async Thread: %#", [NSThread currentThread]);
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_thread_sync(thread, ^{
NSLog(#"Sync Thread: %#", [NSThread currentThread]);
});
});
}
- (void)threadMain
{
// You need the NSPort here because a runloop with no sources or ports registered with it
// will simply exit immediately instead of running forever.
NSPort* keepAlive = [NSPort port];
NSRunLoop* rl = [NSRunLoop currentRunLoop];
[keepAlive scheduleInRunLoop: rl forMode: NSRunLoopCommonModes];
[rl run];
}
#end

In case you have multiple instances of your class, you are overwriting your queue with each new init.
A singleton or a static could be used to resolve the issue.

Related

pass block as parameter in my case

I have a function which takes a block as parameter:
typedef void (^ MyBlock)(int);
-(void)doTask:(MyBlock)theBlock{
...
}
I need to run above function on another thread, I want to use - performSelector:onThread:withObject:waitUntilDone: , my current code:
NSThread *workerThread = [[NSThread alloc] init];
[workerThread start];
[self performSelector:#selector(doTask:)
onThread:workerThread
withObject:???
waitUntilDone:NO];
BUT, How can I pass MyBlock parameter with this approach? (Please don't suggest GCD, I am wondering how can I do with my current code, is it possible?)
This answer assumes you are using ARC. If you are not then you need to do a little more, but overall the answer is the same.
BUT, How can I pass MyBlock parameter with this approach?
A block is an object, you don't need to do anything special. E.g.:
[self performSelector:#selector(doTask:)
onThread:workerThread
withObject:^(int arg){ NSLog(#"block passed: %d", arg); }
waitUntilDone:NO];
HTH
[self performSelector:#selector(someMethod)
onThread:[Your thread]
withObject:[your object]
waitUntilDone:NO];
-(void)someMethod
{
[self doTask:^(int intValue) {
// Do your task
}];
}
First create thread like this so that it is alive and you can call methods from that thread
-(void) myThreadMainMethod: (id) sender {
#autoreleasepool {
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (true) { // add your condition for keeping the thread alive
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
}
NSThread* workerThread = [[NSThread alloc] initWithTarget:self
selector:#selector(myThreadMainMethod:)
object:nil];
[workerThread start];
Then write something like this
-(void)doTask:(MyBlock)theBlock{
NSLog(#"do task called now execute the block");
theBlock(1);
}
MyBlock block1 = ^(int a) {
NSLog(#"integer %i", a);
};
[self performSelector:#selector(doTask:)
onThread:[NSThread mainThread]
withObject:block1
waitUntilDone:NO];

Is it acceptable to daisy NSOperation main into super?

- (void)main
{
IDBAssert0(self.bestCapture.webpCandidate);
self.finished = NO;
self.executing = YES;
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
UIImage *possiblycorrupted = [UIImage imageWithWebPData:self.bestCapture.webpCandidate];
NSTimeInterval webpInterval = [NSDate timeIntervalSinceReferenceDate]-now;
NSDLog(#"it tooke %.2f sec to unpack webp", webpInterval);
self.microblinkCandidate = possiblycorrupted; // data superclass nsoperation processes
[super main];
}
first thing main in the base class does naturally is setting finished to no and executing to yes:
- (void)main
{
self.finished = NO;
self.executing = YES;
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
start = now;
CGSize size = [self.microblinkCandidate size];
IDBAssert0(size.width && size.height);
IDBAssert0(self.microblink);
// this starts async processing
[self.microblink processImage:self.microblinkCandidate
scanningRegion:CGRectMake(0.0, 0.0, 1.0, 1.0)
delegate:self];
while (![self isCancelled])
{
sleep(1);
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
if(now - start > 5) {
// #5677 microblink watchdog to detect hangs
[self cancel];
break;
}
}
[self done];
}
cause it's not an abstract and will be used on its own as well.
the loop is for debug/watchdog purposes only
in the normal operation it's not tripped an operation is done
if this callback:
- (void)scanningViewController: (UIViewController<PPScanningViewController>*)scanningViewController
didOutputResults:(NSArray*)results
{
if([results count]>0) {
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
NSDLog(#"found barcode in %.1fs", now - start);
self.microblinkSuccessHandler();
}else{
IDBAssert0(self.microblinkFailureHandler);
self.microblinkFailureHandler();
}
[self done];
}
is invoked when "processImage:" will have finished (in a timely fashion).
the very base class is
#implementation IDBAsynchronousOperation
#synthesize executing = _executing;
#synthesize finished = _finished;
-(BOOL)isFinished
{
return _finished;
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
-(BOOL)isExecuting
{
return _executing;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (instancetype)init
{
self = [super init];
if (self) {
// self.completionBlock = ^{
// NSDLog(#"image barcode search has finished");
// };
IDBAssert0(sizeof(_executing)<2);
}
return self;
}
-(BOOL)isAsynchronous
{
return YES;
}
#end
You certainly can (and we often do) subclass your own concrete NSOperation subclass.
To make the base class subclassable, you want to make sure that you only perform self.executing = true once. Right now, the main in both the base class and the subclass do it, and you'll therefore be doing it twice. The typical solution is to pull it out of both of those main implementations and do it in start of the base class. Apple suggests that you do this stuff in start, anyway.
Thus having removed the self.finished and self.executing stuff from both main implementations, you can then implement start:
- (void)start {
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
Note, you don't have to call self.finished = false when the operation is starting because that will send an unnecessary KVO.
An unrelated observation:
If you keep the while loop in the base class, I'd suggest exiting the loop if either [self isCancelled] or if the processImage delegate completion methods was called (perhaps you can update some state property to designate when that delegate method was called). Right now, if the processImage finishes before the timeout, it will keep the operation running for the full 5 seconds.
Personally, depending upon how processImage was designed, I probably be inclined excise the while loop entirely. You generally want to avoid any polling like this at all. I might, for example, put the [self done] in the appropriate delegate method and then set up a timer or dispatch_after for the timeout.
- (void)main {
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
start = now;
CGSize size = [self.microblinkCandidate size];
IDBAssert0(size.width && size.height);
IDBAssert0(self.microblink);
// this starts async processing
[self.microblink processImage:self.microblinkCandidate
scanningRegion:CGRectMake(0.0, 0.0, 1.0, 1.0)
delegate:self];
// cancel upon timeout
typeof(self) __weak weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
typeof(self) __strong strongSelf = weakSelf;
if ([strongSelf isExecuting]) {
[strongSelf cancel];
[strongSelf done]; // if canceling calls the delegate method that calls `done`, then you don't need this here
}
});
}

Execute cross thread function one by one

I have some special functions in my project, they will execute cross many threads, such as childContext perform block or AFNetwork response block:
(void)my_function {
dispatch_async(dispatch_get_main_queue(), ^{
NSManagedObjectContext *childContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
childContext.parentContext = self.managedObjectContext;
[childContext performBlock:^{
[self.operationManager POST:URL parameters:nil block:^(AFHTTPRequestOperation *operation, id responseObject) {
//Do something
[childContext performBlock:^{
//Do something
}];
}];
}];
});
}
Now I want to execute them one by one (including all blocks in function). After reading a couple of other answers, some of the Apple documentation, I get some answers:
1. NSRecursiveLock
I can add NSRecursiveLock for each function, but the issue is that I can't lock/unlock cross thread.
(void)my_function {
[MyLock lock]; // Lock here
dispatch_async(dispatch_get_main_queue(), ^{
NSManagedObjectContext *childContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
childContext.parentContext = self.managedObjectContext;
[childContext performBlock:^{
[self.operationManager POST:URL parameters:nil block:^(AFHTTPRequestOperation *operation, id responseObject) {
//Do something
[childContext performBlock:^{
//Do something
[MyLock unlock]; //Unlock here
}];
}];
}];
});
}
2. NSOperation
I can add each function to NSOperationQueue as a NSOperation, and set concurrent operation number to 1, but the issue is that I can't make sure the code in block has been executed even the NSOperation finishes successfully.
3. #synchronized
I can add #synchronized, but has same issue as NSOperation
My suggestion is using NSOperationQueue and Asynchronous NSOperation.
You can make sure operation execute one by one in two ways
addDependency:
Set maxConcurrentOperationCount to 1
With Asynchronous NSOperation,it is you to decide when this Operation is done.
For example
-(void)my_function {
dispatch_async(dispatch_get_main_queue(), ^{
NSManagedObjectContext *childContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType] autorelease];
childContext.parentContext = self.managedObjectContext;
[childContext performBlock:^{
[self.operationManager POST:URL parameters:nil block:^(AFHTTPRequestOperation *operation, id responseObject) {
//Do something
[childContext performBlock:^{
//Do something
//Only mark this NSOperation is done when all task of this function is finished.
}];
}];
}];
});
}
How to make an Asynchronous NSOperation,This is what I usually do,you can also refer it in the document.
Keep two property
#interface AsyncOperation (){
BOOL finished;
BOOL executing;
}
Use these two property to manage Operation state
-(BOOL)isFinished{
return finished;
}
-(BOOL)isExecuting{
return executing;
}
3.Mark Operation start
Note:start function is called on the thread the Operation created
-(void)start{
if ([self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:#"isFinished"];
finished = NO;
[self didChangeValueForKey:#"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:#"isExecuting"];
[NSThread detachNewThreadSelector:#selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:#"isExecuting"];
}
Mark Operation is done
I use this function
-(void)setOperationFinished{
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}

Concurrent NSOperation and how to set isFinished and isExecuting?

I am trying to split up my programs flow using NSOperations. I am using the Parse framework to make a simple messaging app. I want to display some messages and then delete them. The display messages shouldn't be called unless the delete operations is finished so I want to try using an NSQueue and add a displayMessages operation to it and then a deleteMessages operation (named MyOperation below). I understand that concurrent operations means they will only execute one after another in a queue fashion. Below is my code for the delete method. Is there a way to manually tell the operation it is finished i.e. setting isFinished or isExecuting??
// MyOperation.h
#interface MyOperation : NSOperation {
#property (strong, nonatomic) NSMutableArray *toDelete;
}
#end
And the implementation:
// MyOperation.m
#implementation MyOperation
- (id)initWithArray:(NSMutableArray *)array
{
self = [super init];
if (self == nil)
return nil;
_toDelete=array;
}
- (void)main {
if ([self isCancelled]) {
NSLog(#"** operation cancelled **");
}
//how do I get main to finish execution ONLY after deleteAllInBackground has finished?
[PFObject deleteAllInBackground:self.toDelete];
if ([self isCancelled]) {
NSLog(#"** operation cancelled **");
}
NSLog(#"Operation finished");
}
#end
right now this code above won't solve my problem. It will queue up the ops but this one will finish even though the deleteAllInBackground is still running. Would really appreciate some help here! thanks
other possible solution:
-(void)receivedMessage
{
NSLog(#"push!");
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
[self displayMessages];
dispatch_async(dispatch_get_main_queue(), ^(void){
if([self.toDelete count]>0) {
[PFObject deleteAllInBackground:self.toDelete];
}
});
});
}
I would suggest you to use dispatch_async like below;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
dispatch_async(dispatch_get_main_queue(), ^(void){
// Display messages
});
// Delete messages here
});
If you have to use NSOperationQueue then I would suggest you to use KVO to get notification for task completion; When you setup your queue, do this:
[self.deleteQueue addObserver:self forKeyPath:#"delete-operations" options:0 context:NULL];
Then do this in your observeValueForKeyPath:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == self.deleteQueue && [keyPath isEqualToString:#"delete-operations"]) {
if ([self.queue.operations count] == 0) {
// Delete operation done
// Display messages here
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}
[EDIT]
-(void)receivedMessage {
#synchronized(self) {
NSLog(#"push!");
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self displayMessages];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
if([self.toDelete count]>0) {
// this deletion doesn't matter if background or on main thread as it's already in background queue
[PFObject deleteAllInBackground:self.toDelete];
}
});
});
}
}
Use deleteAll instead of deleteAllInBackground if you want to block the current thread until the deletion is complete.

Would I use dispatch in block for MBProgressHud?

In the MBProgressHud documentation it states to use this inside of a dispatch like so:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
Which is completely understandable considering you don't want it to boggle up the main thread. But could I just do this instead when using a block:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error)
{
}
else
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
}];
Or would I still have to use dispatch?
Change your code to be like this:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
NSLog(#"Am I running on the main thread: %#", [NSThread isMainThread] ? #"YES": #"NO");
if (error)
{
}
else
{
}
}];
if it logs "YES" then you don't need to run [MBProgressHUD hideHUDForView:self.view animated:YES]; on the main thread, otherwise you need to use
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
Update:
blocks are run on whatever thread they've been called from, notice following example:
- (void)viewDidLoad {
[super viewDidLoad];
[self longRunningProcessWithCompletionBlock:^{
NSLog(#"Is running on the main thread? %#", [NSThread isMainThread] ? #"YES" : #"NO");
}];
}
- (void)longRunningProcessWithCompletionBlock:(void (^)(void))completionBlock {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this is running on the concurrent thread, so does completionBlock() as it has been called on a concurrent thread.
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3];
completionBlock();
});
}
So Basically the result of above will be "Is running on the main thread? NO"
Again I have exact same call on viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self longRunningProcessWithCompletionBlock:^{
NSLog(#"Is running on the main thread? %#", [NSThread isMainThread] ? #"YES" : #"NO");
}];
}
But this time, I'm calling completionBlock of longRunningProcessWithCompletionBlock on the main thread as follow:
- (void)longRunningProcessWithCompletionBlock:(void (^)(void))completionBlock {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3];
//notice the difference, this time we are calling the completionBlock on the main thread, so that block will be running on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
});
}
This time because we have called the completion block on the main thread, the result will be Is running on the main thread? YES
So in a nutshell, block does not guarantee that they are getting executed on the main thread! but they can guarantee that they will be executed on whatever thread they've been called from.
In your case Parse.com developers are calling the completion handler block of deleteInBackgroundWithBlock on the main thread and that's why you saw that log was "yes".So you just need to call [MBProgressHUD hideHUDForView:self.view animated:YES]; without dispatch_async(dispatch_get_main_queue(), ^{ }); (as it is already on the main thread and this is an extra unnecessary step)

Resources