49

我的应用程序中有一个场景,我想在一个方法中执行一些耗时的任务,其中包括一些数据处理和 UI 更新。我的方法是这样的,

- (void)doCalculationsAndUpdateUIs {

    // DATA PROCESSING 1
    // UI UPDATE 1

    // DATA PROCESSING 2
    // UI UPDATE 2

    // DATA PROCESSING 3
    // UI UPDATE 3
} 

由于这很耗时,我想在后台线程上进行数据处理,使用,

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{

但由于数据处理和 UI 更新都在同一个方法中,我只想在主线程中移动 UI 更新,使用,

dispatch_async(dispatch_get_main_queue(), ^{

最后我的方法看起来像这样,

- (void)doCalculationsAndUpdateUIs {

    // DATA PROCESSING 1 
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 1
    });

    /* I expect the control to come here after UI UPDATE 1 */

    // DATA PROCESSING 2
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 2
    });

    /* I expect the control to come here after UI UPDATE 2 */

    // DATA PROCESSING 3
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI UPDATE 3
    });
}

这真的有效吗?这真的是一个好习惯吗?实现这一目标的最佳方法是什么?

PS 所有这三个操作都是相互关联的。


编辑:对不起,伙计们。我在上面的代码中遗漏了一行。我的实际代码如下所示。

- (void)doCalculationsAndUpdateUIs {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // DATA PROCESSING 1 
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 1
        });

        /* I expect the control to come here after UI UPDATE 1 */

        // DATA PROCESSING 2
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 2
        });

        /* I expect the control to come here after UI UPDATE 2 */

        // DATA PROCESSING 3
        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATE 3
        });
    });
}

再次,我真的为混乱道歉。

4

8 回答 8

116

不,它不会等待,并且您在该示例中执行此操作的方式不是好的做法。

dispatch_async总是异步的。只是您将所有 UI 块排入同一队列,因此不同的块将按顺序运行,但与您的数据处理代码并行运行。

如果您希望更新等待,您可以使用dispatch_sync

// This will wait to finish
dispatch_sync(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
});

另一种方法是将块嵌套入队。我不会推荐它用于多个级别。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // Background work

    dispatch_async(dispatch_get_main_queue(), ^{
        // Update UI

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // Background work

            dispatch_async(dispatch_get_main_queue(), ^{
                // Update UI
            });
        });
    });
});

如果您需要更新 UI 以等待,那么您应该使用同步版本。让后台线程等待主线程是完全可以的。UI 更新应该非常快。

于 2013-07-05T13:42:28.943 回答
12

您必须将主队列调度放在运行计算的块中。例如(这里我创建了一个调度队列并且不使用全局队列):

dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
  // Do some computation here.

  // Update UI after computation.
  dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
  });
});

当然,如果您创建队列,请不要忘记dispatch_release您的目标是 6.0 之前的 iOS 版本。

于 2013-07-05T13:45:40.453 回答
9

您提议doCalculationsAndUpdateUIs的数据处理并将 UI 更新分派到主队列。我假设您doCalculationsAndUpdateUIs在第一次调用它时已将其分派到后台队列。

虽然技术上很好,但这有点脆弱,取决于你每次调用它时记得将它分派到后台:相反,我建议你将你的分派到后台并从同一个内部分派回主队列方法,因为它使逻辑明确且更健壮等。

因此它可能看起来像:

- (void)doCalculationsAndUpdateUIs {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{

        // DATA PROCESSING 1 

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 1
        });

        /* I expect the control to come here after UI UPDATION 1 */

        // DATA PROCESSING 2

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 2
        });

        /* I expect the control to come here after UI UPDATION 2 */

        // DATA PROCESSING 3

        dispatch_async(dispatch_get_main_queue(), ^{
            // UI UPDATION 3
        });
    });
}

至于您是异步调度 UI 更新dispatch_async(后台进程不会等待 UI 更新)还是同步调度dispatch_sync它将等待 UI 更新),问题是为什么要同步进行:您真的想在等待 UI 更新时减慢后台进程,还是希望在 UI 更新发生时继续执行后台进程。

通常,您会dispatch_async像在原始问题中使用的那样异步调度 UI 更新。是的,在某些特殊情况下您需要同步调度代码(例如,您通过在主队列上执行所有更新来同步对某个类属性的更新),但通常情况下,您只需调度 UI 更新异步并继续。如果草率地同步调度代码可能会导致问题(例如死锁),所以我的一般建议是,如果有一些迫切的需要,你可能应该只同步调度 UI 更新,否则你应该设计你的解决方案,以便你可以异步调度它们.


在回答您关于这是否是“实现此目标的最佳方式”的问题时,如果不了解正在解决的业务问题,我们很难说。例如,如果您可能会doCalculationsAndUpdateUIs多次调用它,我可能倾向于使用我自己的串行队列而不是并发全局队列,以确保它们不会相互交叉。或者,如果您可能需要在doCalculationsAndUpdateUIs用户关闭场景或再次调用该方法时取消此操作,那么我可能倾向于使用提供取消功能的操作队列。这完全取决于您要达到的目标。

但是,一般来说,将复杂任务异步分派到后台队列,然后将 UI 更新异步分派回主队列的模式非常常见。

于 2013-07-05T14:33:11.177 回答
1

如果你想运行一个独立的队列操作并且你不关心其他并发操作,你可以使用全局并发队列:

dispatch_queue_t globalConcurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

这将返回一个具有给定优先级的并发队列,如文档中所述:

DISPATCH_QUEUE_PRIORITY_HIGH 分派到队列的项目将以高优先级运行,即队列将被安排在任何默认优先级或低优先级队列之前执行。

DISPATCH_QUEUE_PRIORITY_DEFAULT 分派到队列的项目将以默认优先级运行,即,队列将在所有高优先级队列都被调度之后但在任何低优先级队列被调度之前被调度执行。

DISPATCH_QUEUE_PRIORITY_LOW 分派到队列的项目将以低优先级运行,即队列将在所有默认优先级和高优先级队列都已调度后被调度执行。

DISPATCH_QUEUE_PRIORITY_BACKGROUND 分派到队列的项目将以后台优先级运行,即队列将在所有优先级较高的队列都已安排好后调度执行,并且系统将在具有后台状态的线程上按照 setpriority(2) 运行此队列上的项目(即磁盘 I/O 受到限制,线程的调度优先级设置为最低值)。

于 2014-12-08T10:08:43.833 回答
1

不,它不会等待。

你可以使用performSelectorOnMainThread:withObject:waitUntilDone:.

于 2013-07-05T13:43:51.190 回答
1
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
  // Do some computation here.

  // Update UI after computation.
  dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI on the main thread.
  });
});
于 2016-06-16T07:54:23.503 回答
0

好的做法是:调度组

dispatch_group_t imageGroup = dispatch_group_create();

dispatch_group_enter(imageGroup);
[uploadImage executeWithCompletion:^(NSURL *result, NSError* error){
    // Image successfully uploaded to S3
    dispatch_group_leave(imageGroup);
}];

dispatch_group_enter(imageGroup);
[setImage executeWithCompletion:^(NSURL *result, NSError* error){
    // Image url updated
    dispatch_group_leave(imageGroup);
}];

dispatch_group_notify(imageGroup,dispatch_get_main_queue(),^{
    // We get here when both tasks are completed
});
于 2018-12-12T18:34:50.460 回答
-2

好的,有两种方法可以做到这一点:

// GLOBAL_CONCURRENT_QUEUE


- (void)doCalculationsAndUpdateUIsWith_GlobalQUEUE 
{
    dispatch_queue_t globalConcurrentQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalConcurrentQ, ^{

       // DATA PROCESSING 1
       sleep(1);
       NSLog(@"Hello world chekpoint 1");
       dispatch_sync(dispatch_get_main_queue(), ^{
           // UI UPDATION 1
           sleep(1);
           NSLog(@"Hello world chekpoint 2");
       });

        /* the control to come here after UI UPDATION 1 */
        sleep(1);
        NSLog(@"Hello world chekpoint 3");
        // DATA PROCESSING 2

        dispatch_sync(dispatch_get_main_queue(), ^{
            // UI UPDATION 2
            sleep(1);
            NSLog(@"Hello world chekpoint 4");
        });

        /* the control to come here after UI UPDATION 2 */
        sleep(1);
        NSLog(@"Hello world chekpoint 5");
        // DATA PROCESSING 3

        dispatch_sync(dispatch_get_main_queue(), ^{
            // UI UPDATION 3
            sleep(1);
            NSLog(@"Hello world chekpoint 6");
        });
   });
}



// SERIAL QUEUE
- (void)doCalculationsAndUpdateUIsWith_GlobalQUEUE 
{

    dispatch_queue_t serialQ = dispatch_queue_create("com.example.MyQueue", NULL);
    dispatch_async(serialQ, ^{

       // DATA PROCESSING 1
       sleep(1);
       NSLog(@"Hello world chekpoint 1");

       dispatch_sync(dispatch_get_main_queue(), ^{
           // UI UPDATION 1
           sleep(1);
           NSLog(@"Hello world chekpoint 2");
       });


       sleep(1);
       NSLog(@"Hello world chekpoint 3");
       // DATA PROCESSING 2

       dispatch_sync(dispatch_get_main_queue(), ^{
           // UI UPDATION 2
           sleep(1);
           NSLog(@"Hello world chekpoint 4");
       });  
   });
}
于 2014-03-05T03:59:00.097 回答