【问题标题】:dispatch_after block is not runningdispatch_after 块没有运行
【发布时间】:2019-11-05 14:56:06
【问题描述】:

请考虑这个简单的例子:

- (void)viewDidLoad
{
    [super viewDidLoad];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"BLOCK!!!");

    });

    while (YES)
    {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        NSLog(@"RUN LOOP");
    }
});
}

传递给dispatch_after 的第二次调用(3 秒)的块不会被触发。但是,如果我不使用第一个 dispatch_after(2 秒),那么它会按预期工作。为什么?

我知道,如果我删除了 NSRunLoop 在里面运行的 while 循环,那么它可以工作,但我需要那里的循环

【问题讨论】:

  • 这种方法中的无限循环是个好主意吗?
  • @trojanfoe 有什么好主意?如果我不使用第一个 dispatch_after(仍在运行 nsrunlopp),那么第二个 dispatch_after 会被触发。如果我再添加一层 dispatch_after,为什么它不会触发?
  • 如果你想让东西正常工作,这是个好主意。当你在运行循环时,你也干扰了现有的循环。删除循环,看看会发生什么。
  • 如果我删除循环,那没关系,当然。我知道。但我需要循环,但我不明白为什么它会停止工作
  • 你可能应该在你的问题中提到这一点,因为它显然是罪魁祸首。

标签: ios objective-c grand-central-dispatch nsrunloop dispatch-after


【解决方案1】:

你有代码

  • 安排dispatch_after在主队列上运行;但随后
  • 使用重复调用NSRunLoopwhile 循环阻塞主队列。

这只是阻止主线程执行任何不是直接从主 NSRunLoop 调用的事情。

这个问题有三种解决方案:

  1. 您可以通过将带有while 循环的代码分派到全局(即后台)队列来解决此问题:

    - (void)viewDidLoad{
        [super viewDidLoad];
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
            NSLog(@"OUTER");
    
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"INNER!!!");
            });
    
            while (true) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
            }
        });
    }
    

    这项技术是我们在 GCD 之前的日子里所做的事情的一种排列。如今,这在很大程度上变得无用了。效率太低了。

  2. 您可以使用从NSRunLoop 调度和运行的NSTimer,因此,当您仍在阻塞主队列时,至少会触发计时器。

    - (void)viewDidLoad{
        [super viewDidLoad];
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"OUTER");
    
            [NSTimer scheduledTimerWithTimeInterval:3 repeats:false block:^(NSTimer * _Nonnull timer) {
                NSLog(@"INNER!!!");
            }];
    
            while (true) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
            }
        });
    }
    

    这并不是问题的真正解决方案(您仍然阻止主线程阻止任何未从 NSRunLoop 本身运行的东西),但它阐明了 runloop 的性质。

  3. 或者,显然,最好删除 while 循环:

    - (void)viewDidLoad{
        [super viewDidLoad];
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"OUTER");
    
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"INNER!!!");
            });
        });
    }
    

最重要的是,如今,您几乎从不在线程(或其运行循环)上旋转。它的效率非常低,而 GCD 提供了更优雅的方式来实现所需的效果。

【讨论】:

  • 但是不使用外层dispatch_after为什么不阻塞主线程呢?
  • A runloop 处理线程上的事件,runMode:before: 也允许 GCD 块/项目运行。但是 GCD 强制执行其串行队列的串行性质(并且主队列是串行队列),并且在先前调度的块完成之前,它不允许在该队列上运行任何东西(并且while 循环正在阻止这种情况)。
  • 现在我明白了。如此明显。谢谢!
【解决方案2】:

我在示例中没有看到任何已注册的输入源。我不认为调度算作输入源。

在这种情况下,-runMode:beforeDate: 将立即返回 NO。您设置的无限循环只会旋转而不会退出当前运行。它永远不会回到运行循环的顶部,也永远不会处理任何调度队列。

runMode:beforeDate: 中,定义了行为。

讨论

如果没有输入源或计时器附加到运行循环,则此方法立即退出并返回 NO;否则,它在处理第一个输入源或达到 limitDate 后返回。从运行循环中手动删除所有已知的输入源和计时器并不能保证运行循环将立即退出。 macOS 可能会根据需要安装和删除额外的输入源,以处理针对接收者线程的请求。因此,这些来源可能会阻止运行循环退出。

【讨论】:

  • 它返回YES并且不会立即返回。一旦到达日期,它就会返回控件
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-15
  • 1970-01-01
  • 1970-01-01
  • 2021-11-29
  • 1970-01-01
相关资源
最近更新 更多