【问题标题】:Make sure function runs on main thread only确保函数仅在主线程上运行
【发布时间】:2011-10-21 16:58:58
【问题描述】:

如何确保我的函数只在主线程上运行?它会更新 UI 元素。

这样的功能是否被认为是“坏的”?

-(void)updateSomethingOnMainThread {
    if ( ![[NSThread currentThread] isEqual:[NSThread mainThread]] )
        [self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO];
    else {
        // Do stuff on main thread
    }
}

我这样写是为了避免有第二个功能,最初我是这样写的:

-(void)updateSomethingOnMainThread_real {
    // Do stuff on main thread
}

-(void)updateSomethingOnMainThread {
    [self performSelectorOnMainThread:@selector(updateSomethingOnMainThread_real) withObject:nil waitUntilDone:NO];
}

【问题讨论】:

标签: iphone objective-c


【解决方案1】:

作为 ayoy 的基于方法的 GCD 实现以保证在主线程上执行的替代方案,我在我的代码中使用了以下基于 GCD 的函数(取自 another answer of mine):

void runOnMainThreadWithoutDeadlocking(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

然后您可以在代码中的任何位置使用此辅助函数:

runOnMainThreadWithoutDeadlocking(^{
    // Do stuff that needs to be on the main thread
});

这保证了在封闭块中执行的操作将始终在主线程上运行,无论哪个线程调用它。它添加的代码很少,并且对于需要在主线程上运行哪些代码相当明确。

【讨论】:

  • +1 布拉德,您能简要解释一下“没有死锁”的名称吗?有什么风险,哪些技术无法避免? performSelectorOnMainThreadwaitUntilDoneNO 也不是死锁吗?
  • @Yar - 我在上面链接的答案中解释了这一点:stackoverflow.com/a/5226271/19679。使用同步调度到主队列(类似于-performSelectorOnMainThread: 并将waitUntilDone 设置为YES)如果在已经在主线程上运行的某些东西中调用它会死锁。这让我很惊讶,并导致我创建了上面的辅助函数。 -performSelectorOnMainThread: 没有这个问题,即使 waitUntilDone 设置为 YES。对主队列的异步分派没有相同的死锁问题。
【解决方案2】:

这很好。您也可以使用 GCD 在主线程上执行代码。

查看此 SO 帖子。

GCD to perform task in main thread

【讨论】:

    【解决方案3】:

    我编写了这个简单的#define,我一直在使用它并取得了巨大的成功:

    #define ensureInMainThread(); if (!NSThread.isMainThread) { [self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO];    return; }
    

    这样你的方法,假设它是无参数的,看起来像这样

    - (void) updateTheThings {
          ensureInMainThread();
          [self.dog setTailWag:YES];
          // etc...
    

    【讨论】:

    • 如何处理参数?
    • @Moshe 什么参数?我认为该方法不需要任何参数。 :)
    • 如果我想将带参数的方法传递给不同的线程怎么办?
    • @Moshe,这不处理参数。但是,可以对其进行调整以处理它们,因为您已经收到了 withObject: 部分的电话。您只需要在定义 _X_ 的左侧使用它,在 #_X_ 的右侧使用它,希望对您有所帮助。
    • @Moshe - 我在这里重新发布了一个基于 GCD 的辅助函数作为答案,这就是我如何处理保证某些东西在主线程上的方式。使用块作为输入可以让您在此处运行任意代码,因此我已经取消了我所有的 -performSelectorOnMainThread: 调用并用它替换它们。块让你处理任意数量的参数,我认为只是让一切更容易阅读。
    【解决方案4】:

    或者,您可以使用Grand Central Dispatch API,但不是很方便:

    -(void)updateSomethingOnMainThread {
        void (^doStuff)(void) = ^{
            // stuff to be done
        };
    
        // this check avoids possible deadlock resulting from
        // calling dispatch_sync() on the same queue as current one
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        if (mainQueue == dispatch_get_current_queue()) {
            // execute code in place
            doStuff();
        } else {
            // dispatch doStuff() to main queue
            dispatch_sync(mainQueue, doStuff);
        }
    }
    

    否则,如果不需要同步调用,你可以调用dispatch_async(),这样更简单:

    -(void)updateSomethingOnMainThread {
        dispatch_async(dispatch_get_main_queue(), ^{
            // do stuff
        });
    }
    

    【讨论】:

    • 小心那个代码,如果你在主线程上调用那个函数,它会绑定。
    • 糟糕,对,对不起。所以我想该方法应该检查当前队列是否是主队列,如果不是,则调度它,如果是,则执行该块,对吗?这会使代码比非 GCD 解决方案更复杂 :)
    • 根据洛根的回答,我认为你所拥有的已经是正确的。无需检查。
    • 我只是想知道是否调用 dispatch_sync() 而不是 dispatch_async() 会搞砸什么。毕竟,如果你想等到块返回,它应该是同步的。
    • 知道了:调用此函数并定位当前队列会导致死锁。 - 引用dispatch_sync()上的文档
    猜你喜欢
    • 1970-01-01
    • 2012-02-15
    • 2012-07-19
    • 2016-05-02
    • 2014-05-03
    • 2016-12-19
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多