2

NSRunLoop のrunMode:beforeDateメソッドの正しい使い方に疑問があります。

受信したデリゲート メッセージを処理するセカンダリ バックグラウンド スレッドがあります。

基本的に、バックグラウンド スレッドで実行する必要があるプロセス集中型のロジックがあります。

だから、私は2つのオブジェクトを持っていObjectAますAnotherObjectB.

ObjectA初期化して、それを開始するようにAnotherObjectB指示します。は非同期で動作するため、のデリゲートとして機能します。ここで、デリゲート メッセージで実行する必要があるコードは、バックグラウンド スレッドで実行する必要があります。そのため、NSRunLoop を作成し、実行ループを設定するために次のようなことを行いました。AnotherObjectBAnotherObjectBObjectAAnotherObjectBObjectA

do {
 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (aCondition);

aCondition「完了委任メッセージ」のどこかに設定されています。

私はすべてのデリゲート メッセージを取得しており、それらはそのバックグラウンド スレッドで処理されています。

私の質問は次のとおりです。これは正しいアプローチですか?

私がこれを尋ねる理由[NSDate distantFuture]は、数世紀にわたる日付だからです。基本的に、runLoop は「distantFuture」までタイムアウトしません。それまでは、Mac やこのバージョンの iOS を使用することは絶対にありません。>_<

ただし、実行ループをそれほど長く実行したくありません。適切に終了できるように、最後のデリゲート メッセージが呼び出されたらすぐに実行ループを完了させたいと考えています。

また、間隔を短くして繰り返しタイマーを設定できることも知っていますが、これはポーリングに似ているため、最も効率的な方法ではありません。代わりに、デリゲート メッセージが到着したときにのみスレッドを動作させ、メッセージがないときにスレッドをスリープ状態にしたいと考えています。それで、私が取っているアプローチは正しいアプローチですか、それとも他の方法がありますか。ドキュメントとガイドを読み、それらを読んで理解したことに基づいてこれを設定しました。

ただし、完全に確信が持てない場合は、この素晴らしいコミュニティに意見と確認を求めることをお勧めします.

それでは、よろしくお願いいたします。

乾杯!

4

2 に答える 2

2

コードはドキュメントにあります:

実行ループを終了させたい場合は、このメソッドを使用しないでください。代わりに、他の実行方法のいずれかを使用し、ループ内で独自の他の任意の条件もチェックしてください。簡単な例は次のとおりです。

BOOL shouldKeepRunning = YES;        // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

whereshouldKeepRunningは、プログラム内の別の場所に設定されNOています。

最後の「メッセージ」の後、設定を解除shouldKeepRunningすると(実行ループと同じスレッドで!)、終了するはずです。ここでの重要なアイデアは、実行ループにイベントを送信して停止する必要があるということです。

(また、 NSRunLoop はスレッドセーフではないことに注意してください。使用することになっていると思います-[NSObject performSelector:onThread:...]。)

または、目的に応じて機能する場合は、ディスパッチ キュー/NOperationQueue のバックグラウンドを使用します (ただし、これを行うコードは実行ループに触れないように注意してください。ディスパッチ キュー/NSOperationQueue ワーカー スレッドから NSURLConnection を開始するなどのことは、問題を引き起こす可能性があります)。 )。

于 2012-11-09T20:54:51.017 に答える
1

私がこれを尋ねる理由は、[NSDate distanceFuture] が数世紀にわたる日付だからです。

メソッドrunMode:beforeDate:

  • NOにスケジュールされたソースがない場合は、すぐに戻りRunLoopます。

  • YESイベントが処理されるたびに戻ります。

  • YESlimitDate達したら戻ります。

limitDateそのため、が非常に高い場合でも、イベントが処理されるたびに戻り、limitDateヒットするまで実行し続けません。イベントが処理されない場合は、その時間だけ待機します。limitDateしたがって、メソッドがイベントが発生するのを待つことをあきらめた後のタイムアウトのようなものです。ただし、複数のイベントを連続して処理したい場合は、このメソッドを何度も呼び出す必要があるため、ループが発生します。

ネットワークソケットからタイムアウトでパケットをフェッチすることを考えてください。fetch 呼び出しは、パケットが到着したとき、またはタイムアウトになったときに戻ります。ただし、次のパケットを処理する場合は、fetch メソッドを再度呼び出す必要があります。

残念ながら、次のコードは 2 つの理由から非常に悪いコードです。

// BAD CODE! DON'T USE!
NSDate * distFuture = NSDate.distantFuture;
NSRunLoop * runLoop = NSRunLoop.currentRunLoop;
while (keepRunning) {
    [runLoop runMode:NSDefaultRunLoopMode beforDate:distFuture];
}
  1. でまだnoRunLoopSourceがスケジュールされていない場合はRunLoop、メソッドがすぐに返されて再度呼び出されるため、CPU ができる限り速く、100% の CPU 時間を浪費します。

  2. AutoreleasePool更新されることはありません。自動解放されたオブジェクト (および ARC でさえも) は現在のプールに追加されますが、プールがクリアされないため解放されないため、このループが実行されている限りメモリ消費が増加します。あなたRunLoopSourcesが実際に何をしているか、そして彼らがそれをどのように行っているかによって異なります。

より良いバージョンは次のとおりです。

// USE THIS INSTEAD
NSDate * distFuture = NSDate.distantFuture;
NSRunLoop * runLoop = NSRunLoop.currentRunLoop;
while (keepRunning) @autoreleasepool {
    BOOL didRun = [runLoop runMode:NSDefaultRunLoopMode beforDate:distFuture];
    if (!didRun) usleep(1000);
}

両方の問題を解決します。

  • AnAutoreleasePoolはループの初回実行時に作成され、実行ごとにクリアされるため、時間の経過とともにメモリ消費量が増加することはありません。

  • RunLoopがまったく実行されなかった場合、現在のスレッドは再試行する前に 1 ミリ秒間スリープします。RunLoopSourceこの方法では、noが設定されているため、このコードはミリ秒ごとに 1 回しか実行されないため、CPU 負荷はかなり低くなります。

ループを確実に終了するには、次の 2 つのことを行う必要があります。

  1. に設定keepRunningNOます。keepRunningとして宣言する必要があることに注意してくださいvolatile! そうしないと、コンパイラはチェックを最適化し、ループを無限ループに変える可能性があります。これは、現在の実行コンテキストで変数を変更するコードを認識せず、他のコードが別の場所にあることを認識できないためです (おそらく別のスレッドで)バックグラウンドで変更される場合があります。これが、コンパイラがこれらのバリアを越えて最適化しないため、通常、これらのケース (ロック、ミューテックス、セマフォ、またはアトミック操作) にメモリ バリアが必要な理由です。ただし、その単純なケースでvolatileBOOL、 Obj-C では常にアトミックでありvolatile、コンパイラに「この変数の値は、コンパイル時に変更を確認せずに背後で変更される可能性があるため、常に確認してください」.

  2. 変数がイベント ハンドラー内からではなく、別のスレッドから変更された場合、RunLoopスレッドは呼び出し内でスリープ状態になりrunMode:beforeDate:、イベントが発生するのを待っているRunLoopSource可能性があります。この呼び出しを強制的にすぐに返すには、変数を変更した後にイベントをスケジュールするだけです。performSelector:onThread:withObject:waitUntilDone:これは、以下に示すように実行できます。このセレクターの実行はイベントとしてカウントされRunLoop、メソッドはセレクターが呼び出された後に戻ります。変数が変更されたことを確認し、ループから抜け出します。

volatile BOOL keepRunning;

- (void)wakeMeUpBeforeYouGoGo {
    // Jitterbug
}

// ... In a Galaxy Far, Far Away ...
    keepRunning = NO;
    [self performSelector:@selector(wakeMeUpBeforeYouGoGo) 
        onThread:runLoopThread withObject:nil waitUntilDone:NO];

于 2021-04-30T00:58:18.027 に答える