I need to create an NSThread
object with a C++ class
object passed. The problem is to "exit" this thread from the class the thread operates with.
I tried:
int threadProc(void* mpClass);
struct my_autopool { /// sterge valoarea obiectelor NS definite in functie/constructor
my_autopool() { pool = [[NSAutoreleasePool alloc] init]; }
~my_autopool() { [pool release]; }
NSAutoreleasePool *pool;
};
@interface NS_THD : NSObject {
@public
void* thobj;
}
@end
@implementation NS_THD
-(void)thread_start {
hreadProc(thobj);
}
-(void)thread_close {
printf("Close!\n");
[NSThread close];
}
@end
class CLS_THD {
public:
NSThread* mythread;
NS_THD *trgt;
void CreateThreadOsx(void* mpClass) {
my_autopool ap;
trgt=[[NS_THD alloc] init];
trgt->thobj=(void*)mpClass;
mythread = [[NSThread alloc] initWithTarget:trgt selector:@selector(thread_start) object:nil];
[mythread start];
}
void ExitThreadOsx() {
[mythread close];
//[trgt thread_close];
}
};
class CLS_CPP {
CLS_THD th;
public:
int var_check;
CLS_CPP() {
printf("Hello CLS_CPP!\n");
var_check=77;
th.CreateThreadOsx((void*)this);
}
void change() {
var_check++;
}
void exit() {
th.ExitThreadOsx();
}
};
int threadProc(void* mpClass) {
CLS_CPP *pClass = reinterpret_cast<CLS_CPP*>(mpClass);
while([[NSThread currentThread] isCancelled] == NO) {
printf("Check var %d\n", pClass->var_check);
usleep(333000);
}
return 0;
}
The key issue i'm struggling right now is that it seems there is no way to "close" the NSThread
object.
How to solve this situation?
There are two complications in the design you are trying to implement:
NSThread
after its creation in Foundation framework when you deal with non-Foundation classes. Essentially the only (robust) way to communicate with an NSThread
is by -[NSThread cancel]
and -[NSThread start]
. All other parts of the class (the runloop, input sources, etc..) are available from inside the thread only (or via NSObject
's perform selector family of methods, but this is not really applicable to your scenario). Also the only robust way to finish a(ny) thread is by letting it finish all subroutines it was supposed to execute and i'm not aware of any well-defined implementations which lets you stop a task in the middle of executing it without adjusting the task itself (e.g. via occasional flag check).NSThread
itself was designed to execute tasks wrapped inside of Foundation classes.To get round these problems, you apparently need an instance of NSThread
(and not its NSRunLoop
or sources, because accessing and calling methods of these objects violates contract and can possibly lead to race condition) in your class and get use of the newly introduced constructor of the class -[NSThread initWithBlock:]
. Here is how I would implement such a class:
class CLS_CPP {
void(^workerBlock)(NSTimer*);
NSThread *workerThread;
public:
int var_check;
#pragma mark Special members
CLS_CPP(): workerBlock{ ^(NSTimer *timer){
if (NSThread.currentThread.isCancelled) {
[timer invalidate];
return;
}
printf("Check var %d\n", this->var_check);
} }, var_check{ 77 } {
start();
}
CLS_CPP(const CLS_CPP&) = delete;
CLS_CPP& operator=(const CLS_CPP&) = delete;
~CLS_CPP() {
if (workerThread) {
exit();
}
}
#pragma mark Actions
void change() {
var_check++;
}
void start() {
if (workerThread) {
exit();
}
workerThread = [[NSThread alloc] initWithBlock:^{
[NSTimer scheduledTimerWithTimeInterval:0.333 repeats:YES block:workerBlock];
while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture]) {};
}];
[workerThread start];
}
void exit() {
[workerThread cancel];
// Waits until the thread finishes
while (!workerThread.isFinished) {
std::this_thread::sleep_for(std::chrono::milliseconds(128));
}
workerThread = nil;
}
};
As you can see I have most of special functions deleted (copy operations are deleted explicitly, move operations are deleted implicitly), because it requires extra care in your case (and is a little beyond the question asked). The start
member function makes a new NSThread
object and starts it. exit()
cancels the thread, which state then is checked from within the task itself. The effect of usleep(333000)
is achieved via NSTimer
source, which is more consistent with the Cocoa framework.
The actual task is wrapped inside of the workerBlock
member variable, so you can adjust it accordingly. Provided you compile your code with ARC enabled, you don't need to do anything extra to release the CLS_CPP
resources.
Be advised, that access to var_check
is not synchronised in the implementation, so you will have to take care of it as well (in case you are going to change var_check
from one thread and read from the other)