如何隔离异步请求

How to isolate asynchronous requests

提问人:Giovanni Laganà 提问时间:11/14/2021 最后编辑:Giovanni Laganà 更新时间:11/15/2021 访问量:149

问:

我被困在一个问题上,从以下事实中得出:

  • Android 主线程不应被阻止
  • 我需要执行依赖网络调用(例如,身份验证请求的登录请求 onSuccess)
  • onSuccess 的登录请求 我需要更新 UI(例如从一个 Activity 导航到另一个 Activity)。

我可以通过直接在我的 Activity 中将 Volley 中的代码嵌套并具有以下模式来解决这个问题:

authenticationRequest() {
     onSuccess(): loginRequest() {
           onSuccess(): navigateTo(someActivity)
           onError(): // display some error
     }
     onError(): // display some error
}

这也可能是一个例外情况,因为它在应用程序中只发生一次。 但是,您可以猜到可能还有其他一些请求在响应时触发的情况,但我可能只对在其他一些场景中使用而不感兴趣。BAAB

这就是为什么我认为独立隔离方法并让我仅在必要时编写它们会很棒的原因。

问题在于,这些函数的异步性质不允许我避免依赖关系。相反,依赖关系似乎意味着同步请求或无处不在的重复嵌套代码。我永远无法在我的活动中包含以下内容:

authenticationRequest()
// wait for the result without freezing main thread
loginRequest() 
// wait for the result without freezing main thread
navigateTo(someActivity)

我得到的最接近的解决方案有点冗长,有点脏: 创建可观察的实时数据,并在 response 的值为 onSuccess 或 onError 的情况下执行;因此,在活动中,我有类似的东西:postValue(response)

authenticationRequest()
authenticationResponse.observe(this, {
       loginRequest()
       loginResponse.observe(this, {
             navigateTo(someActivity)
       })
})

但是这个解决方案使可观察数据的管理过于复杂,当我在某些活动中来回切换时,会触发两次。

你有什么更好的建议吗?

编辑说明:函数执行的顺序很重要

Android Kotlin MVVM android-volley networkonmainthread

评论

0赞 broot 11/14/2021
好吧,您刚刚发现了异步代码的问题所在。老实说,到目前为止,规模很小,因为它可能会变得更糟。相对容易实现的解决方案是使用 futures/promise 而不是回调。它们更容易链接。Android 上的正确解决方案是使用协程和可挂起的函数。然后,您可以获得示例中的同步代码,但不会阻塞主线程。
0赞 a_local_nobody 11/14/2021
I am never able to have in my Activity something like:是的,这就是异步请求的本质,它们不是自上而下完成的,并且有办法处理这个问题。即使你使用协程,你仍然需要一种方法来了解结果是什么,你可以与你的UI相关联。
0赞 broot 11/14/2021
另一种解决方案是阻止所有函数,在后台线程中同步执行它们,并仅在需要时跳转到 UI 线程。但我想说这个解决方案比上面提到的更糟糕。
0赞 a_local_nobody 11/14/2021
您可以使用协程来执行服务调用,然后利用实时数据之类的东西来观察该服务调用的结果,然后将 UI 与实际服务调用分离
0赞 Giovanni Laganà 11/14/2021
@broot 那太棒了:我不知道你是否也在 Scala 中编码,但拥有类似的 Future() 构造行为会很方便。你有 Kotlin 中的平行指针吗?

答:

0赞 broot 11/15/2021 #1

你的问题很没有重点。在阅读了您的问题和评论后,您似乎对同步/异步执行有几个担忧。我不会回答你所有的问题,但我至少可以给你一些关于这个话题的见解。

  1. 在理想情况下,在同步和异步之间进行选择的唯一原因是我们(调用方)当前需要什么。如果我们需要执行 和 requests,其中 uses data from 和 uses data from ,那么我们按顺序调用它们。如果它们是独立的,我们会同时调用它们并加入它们。ABCBACB
  2. 同步代码更易于开发和维护,因此,如果我们不需要并行执行,它通常比异步代码更可取。
  3. 从历史上看,出于严格的技术原因,我们被迫使用异步代码。此类代码具有更好的性能,并且不会像 UI 线程那样阻塞关键线程。
  4. 因为 3.我们经常使用异步代码。尽管如此,我们还是不喜欢它,因为 2.我们发明了一些工具,可以帮助我们以更方便的方式处理异步代码,例如futures/premises。它们远非理想,但通常比回调更好。
  5. 最近,我们发明了同时具有同步和异步代码优点的工具,使我们更接近 1 中描述的理想情况。这些用于 JVM 的工具主要是:Kotlin 协程和 Project Loom(尚不可用),但也有其他工具,例如:类星体。使用协程,当保持所有长时间运行的函数可挂起时,我们可以这样编码: 或者: - 以我们当时需要的为准。性能始终与我们异步编码一样。val a = requestA(); val b = requestB(a); requestC(b);launch { requestA() }; launch { requestB() }; launch { requestC() }

评论

0赞 Giovanni Laganà 11/15/2021
第 5 点正是我想要的,但我无法通过协程实现这一点
0赞 broot 11/15/2021
@GiovanniLaganà 它没有直接回答你的问题,但我刚刚在这里写了一个关于类似主题的答案:stackoverflow.com/a/69966845/448875 .我可能应该开始写博客什么的:-/所以答案是:这实际上取决于你用于 IO 的库是什么。一些库本身支持挂起函数。其他提供回调 API,然后您可以将其转换为挂起函数。如果库只支持阻塞 IO,那么我们可以使用它 - 它远非完美,但也不是那么糟糕。withContext(Dispatchers.IO)