【问题标题】:Wrapping Async/Main Thread blocks around code围绕代码包装异步/主线程块
【发布时间】:2018-05-19 22:23:01
【问题描述】:

我正在编写一些代码让用户点击一个按钮登录,登录成功后,如果有可用的数据,请立即再次调用从系统中提取数据。我对需要/不需要将代码包装在不同线程块中的位置有点困惑。鉴于我正在主线程上进行 UI 工作,我可以将所有内容都放在 DispatchQueue.main.async 中吗?这是我的代码流程 - 我想知道是否有人可以审查并让我知道我是否在异步/主线程块中包装了正确的代码结构。

let messageFrame = AlertCreator.progressBarDisplayer(view: self.view, message: "Loading", true)

DispatchQueue.main.async(execute: {

  //Present loading spinner while call is made
  self.view.addSubview(messageFrame)

  //Make an AUTH call using URLSession.shared.dataTask (with callback)                   
  UserSecurityService.AuthenticateUser(username: self.txtUsername.text!, password: self.txtPassword.text!)
     { (authenticationResponse) -> () in

        if (authenticationResponse.Status == ResponseCode.LOGIN_SUCCESS)
         {    
              //If this user logged in successfully, and now need to import data, then do so, otherwise just proceed to the app.                  
              if (authenticationResponse.Value!.HasDataForImport) {

                         //Make ANOTHER async call using URLSession.shared.dataTask (with callback) 
                          UserDataService.GetUserSettings()
                                { response in
                                     //Remove the spinner
                                     messageFrame.removeFromSuperview()

                                    if (response.Status == ResponseCode.OK)
                                    {
                                        //Success, go to dashboard
                                        self.presentHomeViewController()
                                    }
                                    else {
                                        //Show alert failure, with clicking 'ok' firing a callback to take the user to the dashboard
                                    }
                               }
                   }
                  else {
                      //Data does not exist, so just stop the spinner and take the user to the dashboard
                        self.presentHomeViewController()
                  }
          else if (authenticationResponse.Status == ResponseCode.INVALID_USERNAME_PASSWORD) {
                 //User entered the wrong username/password
                  messageFrame.removeFromSuperview()
                 <alert is created/presented here>            
                 }
}) //main dispatch async execute

如果您注意到,我正在进行异步调用以进行身份​​验证,这会在 UI 上执行某些操作(显示微调器以防止任何其他活动,检查登录是否成功,并删除微调器。它还可能会显示一个警报,然后将用户带到视图控制器。

我的具体问题是:

  1. 我是否应该不将整个块包装在 DispatchQueue.main.async 中,但是 而只有两个网络电话?我已经完成了各种组合,其中大多数都有效(还有一些崩溃了),所以我不完全确定我是否只需要 DispatchQueue.main.async,或者需要嵌套在其中的东西,例如 DispatchQueue.global (qos: DispatchQoS.QoSClass.userInteractive).async 也是?
  2. 我应该包装每个网络电话,还是只包装外部电话
  3. 我是否应该将与 UI 相关的内容包装在它们自己的块中,例如当我呈现警报、视图控制器甚至停止时 微调器? 3.

感谢您提供的任何帮助/建议!

【问题讨论】:

  • 简而言之,我会说 1) 在主线程上完成所有与 UI 相关的工作。(显示警报、微调器等)2) 对于任何其他工作,您可以使用全局队列,并相应地设置您的优先级。这将包括(网络电话等)

标签: ios swift asynchronous grand-central-dispatch ui-thread


【解决方案1】:

我尝试将DispatchQueue.mainDispatchQueue.global 存根添加到您的问题中,只是为了展示如何以及何时在队列之间切换。这可能不是一个完整的复制粘贴解决方案,因为我没有你的完整代码,所以我无法编译它。这只是为了展示如何使用DispatchQueue

let messageFrame = AlertCreator.progressBarDisplayer(view: self.view, message: "Loading", true)
        //Present loading spinner while call is made
        self.view.addSubview(messageFrame)

        DispatchQueue.global(qos: .default).async {
            //Make an AUTH call using URLSession.shared.dataTask (with callback)
            UserSecurityService.AuthenticateUser(username: self.txtUsername.text!, password: self.txtPassword.text!)
            { (authenticationResponse) -> () in

                if (authenticationResponse.Status == ResponseCode.LOGIN_SUCCESS)
                {
                    //If this user logged in successfully, and now need to import data, then do so, otherwise just proceed to the app.
                    if (authenticationResponse.Value!.HasDataForImport) {

                        //Make ANOTHER async call using URLSession.shared.dataTask (with callback)
                        UserDataService.GetUserSettings()
                            { response in
                                //Remove the spinner
                                messageFrame.removeFromSuperview()

                                if (response.Status == ResponseCode.OK)
                                {
                                    DispatchQueue.main.async {
                                        //Success, go to dashboard
                                        self.presentHomeViewController()
                                    }
                                }
                                else {
                                    DispatchQueue.main.async {
                                        //Show alert failure, with clicking 'ok' firing a callback to take the user to the dashboard
                                    }
                                }
                        }
                    }
                    else {
                        DispatchQueue.main.async {
                            //Data does not exist, so just stop the spinner and take the user to the dashboard
                            self.presentHomeViewController()
                        }
                    }
                    else if (authenticationResponse.Status == ResponseCode.INVALID_USERNAME_PASSWORD) {
                        DispatchQueue.main.async {
                            //User entered the wrong username/password
                            messageFrame.removeFromSuperview()
                            <alert is created/presented here>
                        }
                    }
                }
                // final = final?.replacingOccurrences(of: "Rs.", with: "")
            }
        }

建议(种类)

我是否应该不将整个块包装在 DispatchQueue.main.async 中,但是 而只是两个网络电话?我做过各种组合, 其中大部分工作(和一些崩溃),所以我不完全确定 如果我只需要 DispatchQueue.main.async,或者需要嵌套的东西 在其中,例如 DispatchQueue.global(qos: DispatchQoS.QoSClass.userInteractive).async 也一样?

您永远不应该将 Web 服务调用包装在 DispatchQueue.main.async 中,而应将其包装在 DispatchQueue.global(qos: .default).async 我假设 服务质量 (QOS)默认,即通常情况下,除非您对backgroundhightlow 有特定要求。 拇指规则,更新主线程上的所有 UI 组件。因为您的主线程与 序列化 MainQueue 相关联,所以切换到 mainQueue 上下文 很重要。

DispatchQueue.main.async 只会异步抓取主线程,DispatchQueue.main.sync 将尝试通过暂停主线程的当前执行来同步抓取主线程。但两者都会让您访问仅主线程因此DispatchQueue.main.async 不是进行非 UI 相关调用(如 Web 服务)的理想方式。

在主线程上制作一个长的、非 UI 相关的任务的问题是,在 Application 的整个生命周期中只能有一个主线程,因为它忙于非 UI 相关的任务,并且因为 Main Queue 是@987654333 @ 所有其他 ui 任务将在主队列中饿死等待主线程,因此将导致 UI 无响应,或者在最坏的情况下(最适合用户:P)导致 iOS 杀死您的应用程序。

我应该包装每个网络电话,还是只包装外部电话

这取决于您的网络通话是如何设计的。如果您使用Alamofire/AFNewtorking 类型的框架,即使您在非ui 线程上调用Web 服务,它们也会在主线程上返回结果。在这种情况下,有必要再次切换到非 ui 线程。因此,在这些情况下,您必须将每个 Web 服务调用包装在 DispatchQueue.global.async 中,否则外部调用应该就可以了 :)

在我上面发布的答案中,假设您的 Web 服务调用完成块使用非 ui 线程返回响应,因此我将两个 Web 服务调用包装在单个 DispatchQueue.global.async 中,如果不是这样,请修改它。

我是否应该将与 UI 相关的东西包装在它们自己的块中,因为 例如,当我显示警报、查看控制器甚至停止 微调器

再次取决于。如果控件在除主线程之外的任何线程上到达 UI 组件更新语句,则应使用 DispatchQueue.main.async 如果您确定控件始终在主线程上到达此类语句,则不需要包装它们。如果您不确定,那么您可以盲目地在这样的语句周围添加一个块,或者通过添加一个 if 条件来检查线程是否是主线程来做出更明智的决定:)

最后一个建议:上下文切换并不便宜,所以要明智地使用它。

希望对你有帮助

【讨论】:

  • Sandeep 我真的非常感谢你抽出时间向我解释这一点。我真的很感激这一点。正如您所说,我按照此操作,Xcode 中的紫色通知消失了(表明它现在是正确的)。如果我有任何其他问题,我可以回复吗?
  • Sandeep 在这种情况下我有嵌套调用,但我注意到整个块只包含在一个 DispatchQueue.global(qos: .default).async { 然后任何地方的 UI 受到影响,那里是一个围绕它的 DispatchQueue.main.async {}。所以基本上我的想法是如果我只是进行异步调用并用 DispatchQueue 包装它们,然后在 DispatchQueue.main.async { 中响应包装任何与 UI 相关的东西,那么我很好吗?
  • @nullhypothesis:一个词宾果游戏:P
  • 小心如果你在主线程上调用网络微调器并且没有可用的互联网,你也需要停止微调器,因为它永远不会释放主线程
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-30
  • 1970-01-01
  • 2014-02-19
  • 1970-01-01
  • 2020-09-02
相关资源
最近更新 更多