在代码周围包装 Async/Main 个线程块

Wrapping Async/Main Thread blocks around code

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

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 上做了一些事情(显示一个微调器以防止任何其他 activity,检查登录是否成功, 并删除微调器。它也可能会显示警报,然后将用户带到视图控制器。

我的问题具体是:

  1. 我不应该把整个块包装在 DispatchQueue.main.async 中,但是 而只是两个网络电话?我已经完成了各种组合,其中大部分都有效(有些崩溃了),所以我不完全确定我是否只需要 DispatchQueue.main.async,或者需要嵌套在其中的东西,例如 [=35] =](qos: DispatchQoS.QoSClass.userInteractive).async 也是?
  2. 我应该包装每个网络调用,还是只包装外部调用
  3. 我是否应该将 UI 相关的东西包装在它们自己的块中,例如当我显示警报、查看控制器,甚至停止时 旋转器? 3.

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

我已经尝试将 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: "")
            }
        }

建议(某种)

Should I not wrap this entire block in DispatchQueue.main.async, but rather only the two web calls? I've done all kinds of combinations, most of which worked (and some that crashed), so I'm not entirely sure if I just need DispatchQueue.main.async, or need something nested inside of it such as a DispatchQueue.global(qos: DispatchQoS.QoSClass.userInteractive).async as well?

您永远不应该将 Web 服务调用包装在 DispatchQueue.main.async 中,您应该将其包装在 DispatchQueue.global(qos: .default).async 我假设 服务质量 (QOS)default 通常情况下是这样,除非您对 backgroundhightlow 有特定的要求ui。 经验法则,在主线程上更新所有 UI 组件。因为您的主线程与 serialized MainQueue 关联,所以 切换到 mainQueue context 很重要。

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

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

Should I wrap each web call, or only the outer one

那要看你的web call是怎么设计的了。如果您使用 Alamofire/AFNewtorking 类框架,即使您在非 ui 线程上调用 Web 服务,它们也会在主线程上 return 结果。在这种情况下,有必要再次切换到非 ui 线程。因此,在这些情况下,您必须将每个 Web 服务调用包装在 DispatchQueue.global.async 中,否则外部调用应该就可以了:)

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

Should I be wrapping the UI-related things in their own blocks, for example when I present alerts, view controllers, or even stop the spinner

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

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

希望对您有所帮助