使用 GCD 进行线程切换,主/背景 线程切换的最佳用例是? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iOS 开发实用技术导航
NSHipster 中文版
http://nshipster.cn/
cocos2d 开源 2D 游戏引擎
http://www.cocos2d-iphone.org/
CocoaPods
http://cocoapods.org/
Google Analytics for Mobile 统计解决方案
http://code.google.com/mobile/analytics/
WWDC
https://developer.apple.com/wwdc/
Design Guides and Resources
https://developer.apple.com/design/
Transcripts of WWDC sessions
http://asciiwwdc.com
Cocoa with Love
http://cocoawithlove.com/
Cocoa Dev Central
http://cocoadevcentral.com/
NSHipster
http://nshipster.com/
Style Guides
Google Objective-C Style Guide
NYTimes Objective-C Style Guide
Useful Tools and Services
Charles Web Debugging Proxy
Smore
iOran
V2EX    iDev

使用 GCD 进行线程切换,主/背景 线程切换的最佳用例是?

  •  1
     
  •   iOran 2016-01-12 10:23:52 +08:00 4097 次点击
    这是一个创建于 3564 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在背景线程请求完数据, block 异步带回需要的数据,此时,应该切换到主线程更新 UI 。更新 UI 可能是一大坨代码,所以我的做法是尽可能将这一坨封装成一个函数。更复杂一些,这个更新 UI 的函数的内部,又要异步去请求数据,请求完数据 block 回来又要切换回主线程更新 UI 。

    最优的推荐做法,应该是进入这个更新 UI 的封装函数之前,切换到 main-thread;还是在这个封装函数内部最开始的位置,切换到 Main-thread?

    我使用 Masonry 来布局,最近老是报:

    This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.

    我知道 layout 应该在主线程做,但难免,这里还是出了问题,那有没有什么最优做法,从编码习惯上就可以规范,保证线程切换不出错呢。所以有了如上问题。

    第 1 条附言    2016-01-13 09:32:58 +08:00

    关于原问题,我个人目前知道的比较好的实践方法是在离开函数前,就切换到主线程,从编程习惯上减少犯错(遗漏)的可能性。

    关于增加的问题(dispatch_group_async/notify 押尾执行),请参考如下 Blog: http://www.jianshu.com/p/5617ad407678

    里面关于 dispatch_group_enter/leave 手动控制 一些列异步任务 完成的使用方法。

    21 条回复    2016-01-28 10:42:55 +08:00
    kobe1941
        1
    kobe1941  
       2016-01-12 10:26:47 +08:00
    你应该改设计,避免在更新 UI 的时候还要再去请求数据,多个数据请求可以用 dispatch_group ,请求都完成后再统一更新 UI
    vincentxue
        2
    vincentxue  
       2016-01-12 10:48:34 +08:00
    楼主最好贴点代码看看,我觉得你代码可能写的有问题。

    是进入这个更新 UI 的封装函数之前,切换到 main-thread;还是在这个封装函数内部最开始的位置,切换到 Main-thread?

    无论是苹果官方 Demo 还是开源库绝大多数都是用的第一种情况。
    iOran
        3
    iOran  
    OP
       2016-01-12 10:56:27 +08:00
    @kobe1941 那如果有些数据是要基于 **用户的选择** 或者 **不同的用户** 而请求的数据,还是有可能碰到这种情况吧?这里应该如何做?

    既然说到 dispatch_group_async/notify ,我还想问问,如果加入到 queue 中的 **一系列任务** 是 **异步的**,此时, notify 到的 block 只是代表 group 已经将这 **一系列任务** 执行完,但它们(一系列任务)的异步执行结果 block 回来可能在 notify 之后,这种情况应该怎么处理?

    例如这种情况,使用 group 真心爽:

    // 获得系统提供的 Global Dispatch Queue 。
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 创建 Dispatch Group ,注意跟 dispatch_queue_create 一样,在 MRC 环境下或者 iOS 6 之前的版本 ARC 环境下需要 用 dispatch_release(group)。

    dispatch_group_t group = dispatch_group_create();

    // 添加任务 Block 到任务队列 queue 中。
    dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
    dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
    dispatch_group_async(group, queue, ^{NSLog(@"blk2");});

    // 通知 group ,那几个任务都结束了。” Done ”消息就属于押尾执行的任务。
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});

    但是如果 **添加任务 Block 到任务队列 queue 中** 变成这样:

    // 添加任务 Block 到任务队列 queue 中。
    dispatch_group_async(group, queue, ^{异步任务 1 ,执行完执行 ^(parameters){} });
    dispatch_group_async(group, queue, ^{异步任务 2 ,执行完执行 ^(parameters){} });
    dispatch_group_async(group, queue, ^{异步任务 3 ,执行完执行 ^(parameters){} });

    问题出现了:它们(异步任务 1 , 2 , 3)的异步执行结果 block 回来可能在 notify 之后。怎么破?
    iOran
        4
    iOran  
    OP
       2016-01-12 10:59:47 +08:00
    @vincentxue 谢谢了。我也侧重第一种情况。应该是我代码看得少了,应该找点开源库看看人家的实现。
    aaaron7
        5
    aaaron7  
       2016-01-12 13:40:29 +08:00
    都一样。你遇到这报错,八成是你代码问题。
    aaaron7
        6
    aaaron7  
       2016-01-12 13:41:55 +08:00
    @iOran
    你爽点也太低了。

    你去用 reactive cocoa ,那才叫爽。
    iOran
        7
    iOran  
    OP
       2016-01-12 14:13:37 +08:00
    @aaaron7 谢谢提醒,反过头会去看看。能否先帮忙顺便解答下 dispatch_group_async 中的任务,如果也是异步的情况下,如何保证 notify 押尾执行。
    aaaron7
        8
    aaaron7  
       2016-01-12 14:21:19 +08:00
    @iOran
    GCD 用得不多,我想到的方法就是利用变量来 track 每一个任务的完成情况,每个任务执行完毕后都检查一次。

    但是用 rac 就可以很优雅的实现这个逻辑:


    // Performs 2 network operations and logs a message to the console when they are
    // both completed.
    //
    // +merge: takes an array of signals and returns a new RACSignal that passes
    // through the values of all of the signals and completes when all of the
    // signals complete.
    //
    // -subscribeCompleted: will execute the block when the signal completes.

    [[RACSignal
    merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]]
    subscribeCompleted:^{
    NSLog(@"They're both done!");
    }];
    EPr2hh6LADQWqRVH
        9
    EPr2hh6LADQWqRVH  
       2016-01-12 14:21:21 +08:00
    非 ios 程序员我 Google 了一下才了解到这个语境下 GCD 的意义。。
    ios 开发论坛药丸的节奏
    raysonx
        10
    raysonx  
       2016-01-12 14:31:56 +08:00
    @avastms +23333333333333
    被屏蔽「使用 *** 进行线程切换,主 /背景 线程切换的最佳用例是?」就笑尿了
    loveuqian
        11
    loveuqian  
       2016-01-12 14:34:34 +08:00 via iPhone
    @avastms
    哈哈哈哈哈哈哈
    huangweihua
        12
    huangweihua  
       2016-01-12 14:44:25 +08:00
    ```
    /**
    主线程回调

    - parameter block: 执行 Block
    */
    func dispatch_async_safely_main_queue(block: ()->()) {
    if NSThread.isMainThread() {
    block()
    } else {
    dispatch_async(dispatch_get_main_queue()) {
    block()
    }
    }
    }
    ```
    vincentxue
        13
    vincentxue  
       2016-01-12 14:50:59 +08:00 via iPhone
    @iOran 你把 group 用错了,但你这样用也不能算错。

    你加进去的三个 nslog 是同步任务,那你直接执行或者直接放在一个队列里就好了,没必要用 group 。

    如果是异步操作,应该配合 group 的 enter/leave 方法使用,这样才能达到你要的效果。
    hzm0318hzm
        14
    hzm0318hzm  
       2016-01-12 14:51:40 +08:00
    @iOran 这样异步使用 group 的话使用 dispatch_group_enter()/dispatch_group_leave() 看你的异步队列到最后能不能执行你收尾的函数
    vincentxue
        15
    vincentxue  
       2016-01-12 14:53:10 +08:00 via iPhone
    @huangweihua 你的方法如果 block 传入 nil 会怎样? oc 是肯定 crash 了。
    iOran
        16
    iOran  
    OP
       2016-01-12 15:54:08 +08:00
    @aaaron7 Merge 的对象,不管时异步/同步都是一样吗?
    iOran
        17
    iOran  
    OP
       2016-01-12 15:56:37 +08:00
    @vincentxue @hzm0318hzm 谢谢两位,知道怎么操作了。后续补答案。

    @vincentxue 三个 nslog 是同步任务是考虑到这些同步任务无所谓执行的先后关系,所以这么丢。
    LGA1150
        18
    LGA1150  
       2016-01-12 21:41:40 +08:00
    @raysonx 几年前贴吧确实屏蔽了这个关键字,不过没有影响到标题
    iOran
        19
    iOran  
    OP
       2016-01-13 09:33:32 +08:00
    @LGA1150 原来这些人是这个意思。。。。
    huangweihua
        20
    huangweihua  
       2016-01-27 17:51:54 +08:00
    @vincentxue 没做测试,传入 nil , nil send_msg 不会 crash 。 swift 的话,后边没有加 ?,所以没办法传入 nil
    vincentxue
        21
    vincentxue  
       2016-01-28 10:42:55 +08:00
    @huangweihua 你的比较特殊,是 block ,执行一个 为 nil 的 block 会 crash 的。你试试。所以回调一般会写成:!completionHandler ?: completionHandler();
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1581 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 16:29 PVG 00:29 LAX 09:29 JFK 12:29
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86