12

私は常に、単体テストに使用する次のコードを作成する方法に興味を持っていました。

特定のスレッドがブロックされているかどうかをチェックするメソッドでNSThreadを拡張することは可能ですか?

現在、 NSConditionを使用しています。Xcodeは、スレッドをブロックするために-waitによって呼び出されるチェーンを表示します。

[NSCondition wait] 
pthread_cond_wait$UNIX2003
_pthread_cond_wait
__psynch_cvwait

NSConditionによって行われたロックをチェックする以外に、可能であれば、他のブロッキング機能(ディスパッチセマフォ、コンディションロック、スリーピングスレッドなど)でも機能するメソッドを高く評価します-Objective-Cの内部についてはわかりません、もしそれらが1つの方法で捕まえられるか、それぞれが独自の方法を必要とする場合。

これが私が達成したいことの簡単な例です。不思議な方法は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]が実行され、ブロックが設定される前にいくつかのコードをハッキングします)が、スレッド/キューがブロックされているという事実)、-[NSThreadisBlocked]メソッドを使用する場合と同じようにこれを回答として受け入れます。

注3:「不可能」などの悪いニュースを疑って、-[条件待機]が実行され、スレッドがブロックされた(注2を参照)という事実をキャッチするためのアイデアはありがたいと思います。答え!

リチャードJ.ロスIIIによる素晴らしい答えへのアドレスの更新1 。残念ながら、彼の答えは私の元の例、つまり私の実際の仕事に近いバージョンでは機能しません(ただし、最初に提供した例と大差ありません-申し訳ありませんが、の最初の版にそれを含めませんでした質問):

// 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がブロックされる前に、テストケースのメインスレッドがこの新しい状態をキャッチすることがあります。 :@ synchronized、NSLockまたはそのようなもの!このトリッキーなケースについて明確に説明したいと思います。私がここに投稿した内容に疑問がある人のために、私は複数のキューやさらに複雑なケースも試していると思います。必要に応じて、さらに多くの例を提供する準備ができています。リチャード、あなたの努力にもう一度感謝します、あなたがこれらの私のポイントを理解するならば、もっと一緒に考えましょう!

更新2

行き止まりのパラドックスがわかります。明らかに、waitingOnConditionの状態を実際に設定するには、この状態の変更をいくつかの同期クロージャ内にラップする必要がありますが、問題は、同期ロックのロックを解除するクロージングを-[conditionの後に呼び出す必要があることです。待機]、しかし、スレッドがすでにブロックされているため、それはできません。繰り返しになりますが、私はそれをかなり明確に説明していると思います。

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 TestProj [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_signaldispatch_semaphore_signal-1

このソリューションは、が「ブロック」されているかどうかのチェックに関する質問に答えることができませんが、既存のフレームワーク内で維持されているインスタンスNSThreadをチェックするために到達していないと仮定すると、到達しようとしているものを提供すると思います。NSThread

于 2013-03-20T04:00:19.183 に答える