В чем разница между основной очередью GCD и основным потоком?

Я прочитал комментарий, так что отправка очереди в основной поток не совпадает с выполнением кода в основном потоке. Если я правильно понял, пользователь говорил, что это

dispatch_async(dispatch_get_main_queue(),
                 ^{
                      // some code
                 });

было не то же самое, что это

[self performSelectorOnMainThread:@selector(doStuff)
                       withObject:nil waitUntilDone:NO];

- (void) doStuff {
  // some code
}

есть ли правда об этом комментарии?

кроме того, что первый код является асинхронным, для меня оба кода выполняются одинаково в главном потоке. Есть ли техническая разница между их?

Я спрашиваю, потому что у меня был код для обновления пользовательского интерфейса с помощью dispatch_async в основном потоке, и он не работал, но когда я изменил его на вторую форму с помощью performSelectorOnMainThread, он работал.

2 ответов


Да, есть разница. Основная очередь отправки-это последовательная очередь. Это означает, что при выполнении задачи, которая была отправлена ему, он не может запускать другие задачи. Это верно, даже если он запускает внутренний цикл событий.

-performSelectorOnMainThread:... работает через источник цикла выполнения. Источники цикла запуска могут запускаться во внутреннем цикле запуска, даже если этот внутренний цикл запуска является результатом предыдущего запуска того же источника.

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

обратите внимание, что это не случай из тупика, вызванного синхронно запрос в основную очередь из основного потока, хотя это также может произойти. С GCD такой синхронный запрос наверняка будет заблокирован. С -performSelectorOnMainThread:..., это не потому что синхронный запрос (waitUntilDone значение YES) просто запускается напрямую.

кстати, вы говорите: "первый код асинхронный", как если бы контраст со вторым кодом. Оба асинхронны, так как вы передали NO на waitUntilDone во втором.


обновление:

рассмотрим такой код:

dispatch_async(dispatch_get_main_queue(), ^{
    printf("outer task, milestone 1\n");
    dispatch_async(dispatch_get_main_queue(), ^{
        printf("inner task\n");
    });
    // Although running the run loop directly like this is uncommon, this simulates what
    // happens if you do something like run a modal dialog or call -[NSTask waitUntilExit].
    [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    printf("outer task, milestone 2\n");
});

это лог:

outer task, milestone 1
outer task, milestone 2
inner task

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

если вы измените внутренний dispatch_async() to dispatch_sync(), тогда программа будет заблокирована.

наоборот, считают:

- (void) task2
{
    printf("task2\n");
}

- (void) task1
{
    printf("task1 milestone 1\n");
    [self performSelectorOnMainThread:@selector(task2) withObject:nil waitUntilDone:NO];
    [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    printf("task1 milestone 2\n");
}

(... in some other method:)
    [self performSelectorOnMainThread:@selector(task1) withObject:nil waitUntilDone:NO];

вот лог:

task1 milestone 1
task2
task1 milestone 2

запуск цикла выполнения внутри -task1 дает возможность для внутреннего -performSelectorOnMainThread:... запустить. В этом большая разница между двумя методами.

если вы измените NO to YES in -task1, это все еще работает без тупика. Это другое отличие. Это потому, что, когда -performSelectorOnMainThread:... С waitUntilDone установить как true, он проверяет, вызывается ли он в основном потоке. Если это так, то он просто непосредственно вызывает селектор прямо там. Как будто это был просто призыв к -performSelector:withObject:.


да, кажется, есть небольшая разница. Давайте напишем код и посмотрим, что это такое:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSLog(@"Starting!");

    CFRunLoopObserverRef o1 = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"runloop phase: %@", NSStringFromRunLoopActivity(activity));
    });
    CFRunLoopAddObserver(CFRunLoopGetMain(), o1, kCFRunLoopDefaultMode);

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_async 1");
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_async 2");
    });

    [self performSelectorOnMainThread:@selector(log) withObject:nil waitUntilDone:NO];
    [self performSelectorOnMainThread:@selector(log) withObject:nil waitUntilDone:NO];

    /*
    NSLog(@"Reentering");
    [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
    NSLog(@"Reexiting");
    */

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        CFRunLoopRemoveObserver(CFRunLoopGetMain(), o1, kCFRunLoopDefaultMode);
        CFRelease(o1);
    });
}

- (void)log {
    NSLog(@"performSelector");
}

в этом кусочке кода мы собираемся установить RunLoop Observer добавить логирования при runloop спинов. Это поможет нам понять, когда выполняется наш асинхронный код. (the NSStringFromRunLoopActivity() функция является пользовательской функцией, чтобы просто повернуть activity значение в строку; его реализация неинтересна)

мы собираемся отправьте две вещи в главную очередь, и мы отправим две log селекторы к основному потоку. Обратите внимание, что мы dispatch_async - ing перед -performSelector: звонок.