40

NSRunLoopに関する Apple のドキュメントには、フラグが設定されるのを待っている間に実行を中断するサンプル コードがあります。

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

私はこれを使用しており、動作しますが、パフォーマンスの問題を調査する際に、このコードまで追跡しました。私はほぼ同じコードを使用します(フラグの名前が異なるだけです:)そしてNSLog、フラグが設定された後に(別の方法で)行に a を配置し、その後に行を配置するwhile()と、一見ランダムです2 つのログ ステートメントの間で数秒間待機します。

遅延は、低速または高速のマシンで異なるようには見えませんが、実行ごとに異なり、少なくとも数秒から最大 10 秒です。

次のコードでこの問題を回避しましたが、元のコードが機能しないのは正しくないようです。

NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (webViewIsLoading && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil])
  loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.1];

このコードを使用すると、フラグを設定するときと while ループの後のログ ステートメントの間隔が一貫して 0.1 秒未満になります。

元のコードがこの動作を示す理由を知っている人はいますか?

4

7 に答える 7

36

Runloopsは、何かが起こるだけのちょっとした魔法の箱になることがあります。

基本的に、runloopにいくつかのイベントを処理してから戻るように指示しています。または、タイムアウトになる前にイベントを処理しない場合は戻ります。

0.1秒のタイムアウトを使用すると、タイムアウトが頻繁に発生します。runloopが起動し、イベントを処理せず、0.1秒で戻ります。時折、イベントを処理する機会があります。

distantFutureタイムアウトを使用すると、runloopはイベントを処理するまで待機します。したがって、それがあなたに戻ったとき、それはある種のイベントを処理したばかりです。

短いタイムアウト値は、無限のタイムアウトよりもかなり多くのCPUを消費しますが、たとえば、runloopが実行されているプロセス/スレッドを終了する場合など、短いタイムアウトを使用するのには十分な理由があります。フラグが変更されたこと、およびフラグをできるだけ早く救済する必要があること。

runloopオブザーバーをいじって、runloopが何をしているかを正確に確認することをお勧めします。

詳細については、このAppleドキュメントを参照してください。

于 2008-09-29T19:40:03.957 に答える
17

さて、私はあなたに問題を説明しました、ここに可能な解決策があります:

@implementation MyWindowController

volatile BOOL pageStillLoading;

- (void) runInBackground:(id)arg
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // Simmulate web page loading
    sleep(5);

    // This will not wake up the runloop on main thread!
    pageStillLoading = NO;

    // Wake up the main thread from the runloop
    [self performSelectorOnMainThread:@selector(wakeUpMainThreadRunloop:) withObject:nil waitUntilDone:NO];

    [pool release];
}


- (void) wakeUpMainThreadRunloop:(id)arg
{
    // This method is executed on main thread!
    // It doesn't need to do anything actually, just having it run will
    // make sure the main thread stops running the runloop
}


- (IBAction)start:(id)sender
{
    pageStillLoading = YES;
    [NSThread detachNewThreadSelector:@selector(runInBackground:) toTarget:self withObject:nil];
    [progress setHidden:NO];
    while (pageStillLoading) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    [progress setHidden:YES];
}

@end

startは進行状況インジケーターを表示し、メイン スレッドを内部実行ループに取り込みます。他のスレッドが完了したことを発表するまで、そこにとどまります。メインスレッドをウェイクアップするために、メインスレッドをウェイクアップする以外の目的のない関数を処理させます。

これは、その方法の 1 つにすぎません。メインスレッドで投稿および処理される通知が望ましい場合があります(他のスレッドが登録することもできます)が、上記の解決策は私が考えることができる最も簡単な方法です。ところで、実際にはスレッドセーフではありません。本当にスレッドセーフであるためには、ブール値へのすべてのアクセスは、いずれかのスレッドから NSLock オブジェクトによってロックされる必要があります (ロックによって保護された変数は、POSIX 標準に従って暗黙的に揮発性であるため、そのようなロックを使用すると、「揮発性」も時代遅れになります。ただし、C 標準はロックについて認識していないため、ここでは volatile のみがこのコードの動作を保証できます; GCC は、ロックによって保護された変数に volatile を設定する必要はありません)。

于 2008-10-07T11:44:39.603 に答える
11

一般に、ループ内で自分でイベントを処理している場合、それは間違っています。私の経験では、それは多くの厄介な問題を引き起こす可能性があります。

進行状況パネルを表示するなど、モーダルに実行したい場合は、モーダルに実行してください。先に進み、NSApplication メソッドを使用し、プログレス シートに対してモーダルを実行し、ロードが完了したらモーダルを停止します。http://developer.apple.com/documentation/Cocoa/Conceptual/WinPanel/Concepts/UsingModalWindows.htmlなどの Apple のドキュメントを参照してください。

ロード中にビューを起動したいだけで、モーダルにしたくない場合 (たとえば、他のビューがイベントに応答できるようにしたい場合) は、もっと単純なことを行う必要があります。たとえば、次のようにします。

- (IBAction)start:(id)sender
{
    pageStillLoading = YES;
    [NSThread detachNewThreadSelector:@selector(runInBackground:) toTarget:self withObject:nil];
    [progress setHidden:NO];
}

- (void)wakeUpMainThreadRunloop:(id)arg
{
    [progress setHidden:YES];
}

これで完了です。実行ループを制御し続ける必要はありません!

-ウィル

于 2008-10-25T22:52:19.743 に答える
10

フラグ変数を設定し、実行ループにすぐに通知させたい場合は、実行ループに-[NSRunLoop performSelector:target:argument:order:modes:フラグを false に設定するメソッドを呼び出すように指示するだけです。これにより、実行ループがすぐにスピンし、メソッドが呼び出され、フラグがチェックされます。

于 2008-09-30T02:09:30.230 に答える
5

コードで、現在のスレッドは変数が 0.1 秒ごとに変更されたかどうかをチェックします。Apple のコード例では、変数を変更しても何の効果もありません。ランループは、何らかのイベントを処理するまで実行されます。webViewIsLoading の値が変更された場合、イベントは自動的に生成されないため、ループにとどまります。なぜループから抜け出すのでしょうか? 他のイベントを処理するまでそこにとどまり、それから抜け出します。これは、1、3、5、10、または 20 秒で発生する可能性があります。そしてそれが起こるまで、それは実行ループから抜け出さないので、この変数が変更されたことに気付かないでしょう. IOW あなたが引用した Apple コードは不確定です。

問題を考え直したほうがいいと思います。変数の名前が webViewIsLoading であるため、Web ページが読み込まれるのを待ちますか? そのためにWebkitを使用していますか?そのような変数も、投稿したコードも必要ないと思います。代わりに、アプリを非同期でコーディングする必要があります。「Web ページのロード プロセス」を開始してからメイン ループに戻り、ページのロードが終了したらすぐに、メイン スレッド内で処理される通知を非同期に投稿し、すぐに実行する必要があるコードを実行する必要があります。ロードが終了しました。

于 2008-10-06T11:31:19.927 に答える
2

管理しようとしているときに、同様の問題が発生しましたNSRunLoops。クラス参照ページの議論には次のように書かれています:runMode:beforeDate:

実行ループに入力ソースまたはタイマーが接続されていない場合、このメソッドはすぐに終了します。それ以外の場合は、最初の入力ソースが処理されるか、limitDate に達した後に戻ります。既知のすべての入力ソースとタイマーを実行ループから手動で削除しても、実行ループが終了する保証はありません。Mac OS X は、受信者のスレッドを対象とする要求を処理するために、必要に応じて追加の入力ソースをインストールおよび削除する場合があります。したがって、これらのソースは実行ループの終了を妨げる可能性があります。

NSRunLoopおそらくOS X自体によって入力ソースが接続されておりrunMode:beforeDate:、その入力ソースが何らかの入力を処理するか削除されるまでブロックされていると思います。あなたの場合、これが発生するのに「数秒から最大10秒」かかりました。その時点でrunMode:beforeDate:ブール値が返され、が再び実行され、に設定されているwhile()ことが検出され、ループが終了します。shouldKeepRunningNO

runMode:beforeDate:入力ソースがアタッチされているかどうか、または入力を処理したかどうかに関係なく、改良によりは 0.1 秒以内に戻ります。これは経験に基づいた推測ですが (私は実行ループの内部の専門家ではありません)、あなたの改良が状況を処理する正しい方法だと思います。

于 2008-09-29T20:08:11.470 に答える
1

2 番目の例は、時間間隔 0.1 内で実行ループの入力を確認するためにポーリングするときに回避できます。

ときどき、最初の例の解決策を見つけます。

BOOL shouldKeepRunning = YES;        // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]);
于 2013-08-14T01:48:16.603 に答える