Можно ли проверить, заблокирован ли NSThread?

Мне всегда было интересно, как написать следующий код, чтобы использовать его для модульного тестирования:

Можно ли расширить NSThread с помощью метода, который бы проверял, заблокирован ли какой-либо поток?

Сейчас я работаю с NSCondition: Xcode показывает мне цепочку, вызываемую -wait, чтобы заблокировать поток:

[NSCondition wait] 
pthread_cond_wait$UNIX2003
_pthread_cond_wait
__psynch_cvwait

Помимо проверки блокировок, выполняемых NSCondition, если это даже возможно, я бы очень признателен за то, что метод работает и для любых других возможностей блокировки (отправка семафоров, блокировок условий, спаренных потоков и т.д.). Я понятия не имею о Objective-C, если возможно, они могут быть схвачены одним методом или каждый из них нуждается в своих собственных.

Вот простой пример того, чего я хотел бы достичь. Таинственный метод называется isBlocked.

// Some test case
// ...

__block NSThread *thread;
NSCondition *condition = [NSCondition alloc] init];

dispatch_async(someQueue(), ^{
    thread = NSThread.currentThread; 

    [condition lock];
    [condition wait];
    [condition unlock];
});

while(1) {
    NSLog(@"Thread is blocked: %d", thread.isBlocked);
}

Примечание: Я плохо разбираюсь в C и всех этих низкоуровневых POSIX-материалах, поэтому, пожалуйста, будьте подробными.

Примечание 2:. Я также заинтересован в решениях, работающих с диспетчерскими очередями: если кто-то может показать мне, как проверить факт, что someQueue() заблокирован - [NSCondition wait] (а не факт, что он будет заблокирован (fx взломает какой-то код раньше - [условие wait] запущено, и блок установлен), но тот факт, что поток/очередь заблокирован), я соглашусь с этим как ответ, как я бы сделал с работой - [NSThread isBlocked].

Примечание 3: Подозревая плохие новости, такие как "это невозможно", я утверждаю, что любые идеи о том, чтобы поймать факт, что - [условие ожидания], были выполнены, и поток был заблокирован (см. Примечание 2) оцениваются и могут быть также приняты в качестве ответа!

ОБНОВЛЕНИЕ 1 в адресе к приятному ответу Ричарда Дж. Росса III. К сожалению, его ответ не работает в моем оригинальном примере, версия, которая ближе к моей реальной работе (хотя она не сильно отличается от примера, который я изначально предоставил, извините, что я не включил его в первый выпуск вопрос):

// Example

// Here I've bootstrapped Richard isLocking categories for both NSThread and NSCondition
// ...

// somewhere in SenTesting test case...
__block NSThread *thread;
NSCondition *condition = [NSCondition alloc] init];
__block BOOL wePassedBlocking = NO;

dispatch_async(someQueue(), ^{
    thread = NSThread.currentThread; 

    [condition lock];
    [condition wait];
    [condition unlock];

    wePassedBlocking = YES; // (*) This line is occasionally never reached!
});

while(!thread.isWaitingOnCondition); // I want this loop to exit after the condition really locks someQueue() and _thread_ __.

// sleep(1);

[condition lock];
[condition broadcast]; // BUT SOMETIMES this line is called before -[condition wait] is called inside someQueue() so the entire test case becomes blocked!
[condition unlock];

while(!wePassedBlocking); // (*) And so this loop occasionally never ends!

Если я раскомментирую sleep (1), тест начинает работать очень стабильно без каких-либо случайных блокировок!

Это приводит нас к проблеме, что категория Ричарда устанавливает состояние ровно на одну строку до фактической блокировки, что означает, что основной поток иногда отслеживает это новое состояние прежде чем мы действительно заблокируем someQueue/thread, потому что код Richard не содержит механизмов синхронизации: @synchronized, NSLock или что-то в этом роде! Надеюсь, я даю ясное объяснение этому сложному делу. Для тех, кто сомневается в том, что я здесь разместил, я бы сказал, что я также экспериментировал с несколькими очередями и даже более сложными делами, и при необходимости я готов предоставить больше примеров. Ричард, еще раз спасибо за ваши усилия, подумайте больше, если вы поймете эти мои моменты!

ОБНОВЛЕНИЕ 2

Я вижу парадокс тупика: очевидно, чтобы действительно установить состояние waitingOnCondition, нам нужно обернуть это изменение состояния внутри некоторых закрытий синхронизации, но проблема в том, что закрытие, разблокировка блокировка синхронизации, должна вызываться после - [условие wait], но она не может, потому что поток уже заблокирован. Опять же, я надеюсь, что описываю это довольно ясно.

Ответ 1

Здесь вы идете! Он не будет обнаруживать, что нить ждет от чего-либо другого, кроме -[NSCondition wait], но его можно легко расширить, чтобы обнаружить другие виды ожидания.

Вероятно, это не лучшая реализация, но на самом деле она работает и будет делать то, что вам нужно.

#import <objc/runtime.h>

@implementation NSThread(isLocking)

static int waiting_condition_key;

-(BOOL) isWaitingOnCondition {
    // here, we sleep for a microsecond (1 millionth of a second) so that the
    // other thread can catch up,  and actually call 'wait'. This time
    // interval is so small that you will never notice it in an actual
    // application, it just here because of how multithreaded
    // applications work.    
    usleep(1);

    BOOL val = [objc_getAssociatedObject(self, &waiting_condition_key) boolValue];

    // sleep before and after so it works on both edges
    usleep(1);

    return val;
}

-(void) setIsWaitingOnCondition:(BOOL) value {
        objc_setAssociatedObject(self, &waiting_condition_key, @(value), OBJC_ASSOCIATION_RETAIN);
}

@end

@implementation NSCondition(isLocking)

+(void) load {
    Method old = class_getInstanceMethod(self, @selector(wait));
    Method new = class_getInstanceMethod(self, @selector(_wait));

    method_exchangeImplementations(old, new);
}

-(void) _wait {
    // this is the replacement for the original wait method
    [[NSThread currentThread] setIsWaitingOnCondition:YES];

    // call  the original implementation, which now resides in the same name as this method
    [self _wait];

    [[NSThread currentThread] setIsWaitingOnCondition:NO];
}

@end

int main()
{
    __block NSCondition *condition = [NSCondition new];

    NSThread *otherThread = [[NSThread alloc] initWithTarget:^{
        NSLog(@"Thread started");

        [condition lock];
        [condition wait];
        [condition unlock];

        NSLog(@"Thread ended");
    } selector:@selector(invoke) object:nil];
    [otherThread start];

    while (![otherThread isWaitingOnCondition]);

    [condition lock];
    [condition signal];
    [condition unlock];

    NSLog(@"%i", [otherThread isWaitingOnCondition]);
}

Вывод:

2013-03-20 10:43:01.422 TestProj[11354:1803] Thread started
2013-03-20 10:43:01.424 TestProj[11354:1803] Thread ended
2013-03-20 10:43:01.425 TestProj[11354:303] 0

Ответ 2

Вот решение, использующее dispatch_semaphore_t

PGFoo.h

#import <Foundation/Foundation.h>

@interface PGFoo : NSObject

- (void)longRunningAsynchronousMethod:(void (^)(NSInteger result))completion;

@end

PGFoo.m

#import "PGFoo.h"

@implementation PGFoo

- (void)longRunningAsynchronousMethod:(void (^)(NSInteger))completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(5);
        completion(1);
    });
}

@end

Методы испытаний

- (void)testThatFailsBecauseItIsImpatient {
    PGFoo *foo = [[PGFoo alloc] init];
    __block NSInteger theResult = 0;
    [foo longRunningAsynchronousMethod:^(NSInteger result) {
        theResult = result;
    }];

    STAssertEquals(theResult, 1, nil);
}

- (void)testThatPassesBecauseItIsPatient {
    PGFoo *foo = [[PGFoo alloc] init];
    __block NSInteger theResult = 0;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [foo longRunningAsynchronousMethod:^(NSInteger result) {
        theResult = result;
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    STAssertEquals(theResult, 1, nil);
}

Используя dispatch_semaphore_t, вы можете "отслеживать", заблокирован ли поток, ожидающий этого семафора. Для каждого вызова dispatch_semaphore_wait счет семафора уменьшается и поток ожидает, пока не будет вызван вызов dispatch_semaphore_signal, когда вызывается dispatch_semaphore_signal, счет семафора увеличивается, если счет увеличивается до значения, превышающего -1 поток продолжается.

Это решение не может ответить на ваш вопрос о проверке того, заблокирован ли NSThread, но я думаю, что он обеспечивает то, к чему вы достигаете, если вы не дойдете до проверки экземпляров NSThread, которые поддерживаются в пределах существующей структуры.