12

我一直对如何编写以下代码以将其用于单元测试感兴趣:

是否可以使用检查特定线程是否被阻塞的方法来扩展 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在 -[condition wait] 运行并设置块之前破解一些代码),但线程/队列被阻塞的事实),我会像处理 -[NSThread isBlocked] 方法一样接受这个作为答案.

注 3:怀疑像“不可能”这样的坏消息,我声称任何关于捕获 -[condition wait] 已运行并且线程被设置为阻塞(见注 2)这一事实的想法都值得赞赏,也可以被接受为一个答案!

更新 1以解决 Richard J. Ross III 的好答案。不幸的是,他的回答在我原来的例子中不起作用,这个版本更接近我的真实工作(尽管它与我最初提供的例子没有太大区别——很抱歉我没有将它包含在第一版问题):

// Example

// Here I've bootstrapped Richard's 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)测试开始工作非常稳定,没有任何偶尔的锁定!

这导致我们遇到问题,Richard 的类别确实在实际阻塞完成之前设置状态正好一行,这意味着有时测试用例的主线程在我们实际阻塞someQueue/thread之前捕获这个新状态,因为 Richard 的代码不包含任何同步机制:@synchronized、NSLock 或类似的东西!我希望我能清楚地解释这个棘手的案例。对于任何对我在这里发布的内容有疑问的人,我想说我也一直在尝试多个队列甚至更复杂的案例,如果需要,我准备提供更多示例。理查德,再次感谢您的努力,如果您理解我的这些观点,让我们一起思考更多!

更新 2

我看到了一个死胡同:显然,要真正设置waitingOnCondition的状态,我们需要将此状态的更改包装在一些同步闭包中,但问题是关闭同步锁,解锁同步锁,应该在 -[condition wait],但它不能,因为线程已经被阻塞。再次,我希望我描述得很清楚。

4

2 回答 2

4

干得好!它不会检测除 之外的任何其他线程正在等待-[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's 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] 线程开始
2013-03-20 10:43:01.424 TestProj[11354:1803] 线程结束
2013-03-20 10:43:01.425 测试项目 [11354:303] 0
于 2013-03-20T12:00:02.940 回答
0

这是使用的解决方案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线程继续的值,则信号量的计数会增加。

该解决方案无法回答您有关检查 anNSThread是否“被阻止”的问题,但我认为它提供了您想要的内容,假设您没有到达检查NSThread现有框架内维护的实例。

于 2013-03-20T04:00:19.183 回答