NetworkOnMainThreadException 是否对协程中的网络调用有效?
Is NetworkOnMainThreadException valid for a network call in a coroutine?
我正在 Kotlin 中为 Android 构建一个简单的演示应用程序,它使用 Jsoup 检索网页的标题。我正在使用 Dispatchers.Main
作为上下文进行网络调用。
我对协同程序的理解是,如果我在 Dispatchers.Main
上调用 launch
它会在主线程上 执行 运行,但会暂停执行以免阻塞线程。
我对android.os.NetworkOnMainThreadException
的理解是因为网络操作繁重,在主线程运行时会阻塞
所以我的问题是,鉴于协程不会阻塞它所在的线程 运行,NetworkOnMainThreadException
真的有效吗?下面是一些在 Jsoup.connect(url).get()
:
处抛出给定异常的示例代码
class MainActivity : AppCompatActivity() {
val job = Job()
val mainScope = CoroutineScope(Dispatchers.Main + job)
// called from onCreate()
private fun printTitle() {
mainScope.launch {
val url ="https://kotlinlang.org"
val document = Jsoup.connect(url).get()
Log.d("MainActivity", document.title())
// ... update UI with title
}
}
}
我知道我可以简单地 运行 使用 Dispatchers.IO
上下文并将此结果提供给 main/UI 线程,但这似乎避开了协程的一些实用程序。
作为参考,我使用的是 Kotlin 1.3。
My understanding of coroutines is that if I call launch on the Dispatchers.Main it does run on the main thread, but suspends the execution so as to not block the thread.
为了不阻塞线程而暂停执行的唯一点是标记为 suspend
的方法 - 即暂停方法。
由于Jsoup.connect(url).get()
不是挂起方法,它会阻塞当前线程。当你使用 Dispatchers.Main
时,当前线程是主线程,你的网络操作直接在主线程上运行,导致 NetworkOnMainThreadException
.
像您的 get()
方法这样的阻塞工作可以通过将其包装在 withContext()
中来暂停, 是 一种暂停方法并确保 Dispatchers.Main
在方法运行时未被阻塞。
mainScope.launch {
val url ="https://kotlinlang.org"
val document = withContext(Dispatchers.IO) {
Jsoup.connect(url).get()
}
Log.d("MainActivity", document.title())
// ... update UI with title
}
协程挂起不是神奇地 "unblocks" 现有阻塞网络调用的功能。它严格来说是一个 cooperative 功能,需要代码显式调用 suspendCancellableCoroutine
。因为您正在使用一些预先存在的阻塞 IO API,协程会阻塞其调用线程。
要真正利用可挂起代码的强大功能,您必须使用非阻塞 IO API,它可以让您发出请求并提供 API 将在结果为 API 时调用的回调准备好。例如:
NonBlockingHttp.sendRequest("https://example.org/document",
onSuccess = { println("Received document $it") },
onFailure = { Log.e("Failed to fetch the document", it) }
)
有了这种API,无论您是否使用协程,都不会阻塞任何线程。然而,与阻塞 API 相比,它的用法相当笨拙和混乱。这就是协程可以帮助您的:它们允许您以完全相同的形式继续编写代码,就好像它是阻塞的,只是它不是。要获得它,您必须首先编写一个 suspend fun
将您拥有的 API 转换为协程挂起:
suspend fun fetchDocument(url: String): String = suspendCancellableCoroutine { cont ->
NonBlockingHttp.sendRequest(url,
onSuccess = { cont.resume(it) },
onFailure = { cont.resumeWithException(it) }
)
}
现在你的调用代码回到这个:
try {
val document = fetchDocument("https://example.org/document")
println("Received document $document")
} catch (e: Exception) {
Log.e("Failed to fetch the document", e)
}
相反,如果您可以保留阻塞网络 IO,这意味着每个并发网络调用都需要一个专用线程,那么如果没有协程,您将不得不使用异步任务之类的东西,Anko 的 bg
等。这些方法还要求您提供回调,因此协程可以再次帮助您保持自然的编程模型。核心协程库已经包含了你需要的所有部分:
- 一个专门的弹性线程池,如果当前所有线程都被阻塞,它总是启动一个新线程(可通过
Dispatchers.IO
访问)
withContext
原语,它允许您的协程从一个线程跳转到另一个线程然后返回
使用这些工具,您可以简单地编写
try {
val document = withContext(Dispatchers.IO) {
JSoup.connect("https://example.org/document").get()
}
println("Received document $it")
} catch (e: Exception) {
Log.e("Failed to fetch the document")
}
当您的协程到达 JSoup 调用时,它将释放 UI 线程并在 IO 线程池中的线程上执行此行。当它解除阻塞并得到结果时,协程将跳回到 UI 线程。
我正在 Kotlin 中为 Android 构建一个简单的演示应用程序,它使用 Jsoup 检索网页的标题。我正在使用 Dispatchers.Main
作为上下文进行网络调用。
我对协同程序的理解是,如果我在 Dispatchers.Main
上调用 launch
它会在主线程上 执行 运行,但会暂停执行以免阻塞线程。
我对android.os.NetworkOnMainThreadException
的理解是因为网络操作繁重,在主线程运行时会阻塞
所以我的问题是,鉴于协程不会阻塞它所在的线程 运行,NetworkOnMainThreadException
真的有效吗?下面是一些在 Jsoup.connect(url).get()
:
class MainActivity : AppCompatActivity() {
val job = Job()
val mainScope = CoroutineScope(Dispatchers.Main + job)
// called from onCreate()
private fun printTitle() {
mainScope.launch {
val url ="https://kotlinlang.org"
val document = Jsoup.connect(url).get()
Log.d("MainActivity", document.title())
// ... update UI with title
}
}
}
我知道我可以简单地 运行 使用 Dispatchers.IO
上下文并将此结果提供给 main/UI 线程,但这似乎避开了协程的一些实用程序。
作为参考,我使用的是 Kotlin 1.3。
My understanding of coroutines is that if I call launch on the Dispatchers.Main it does run on the main thread, but suspends the execution so as to not block the thread.
为了不阻塞线程而暂停执行的唯一点是标记为 suspend
的方法 - 即暂停方法。
由于Jsoup.connect(url).get()
不是挂起方法,它会阻塞当前线程。当你使用 Dispatchers.Main
时,当前线程是主线程,你的网络操作直接在主线程上运行,导致 NetworkOnMainThreadException
.
像您的 get()
方法这样的阻塞工作可以通过将其包装在 withContext()
中来暂停, 是 一种暂停方法并确保 Dispatchers.Main
在方法运行时未被阻塞。
mainScope.launch {
val url ="https://kotlinlang.org"
val document = withContext(Dispatchers.IO) {
Jsoup.connect(url).get()
}
Log.d("MainActivity", document.title())
// ... update UI with title
}
协程挂起不是神奇地 "unblocks" 现有阻塞网络调用的功能。它严格来说是一个 cooperative 功能,需要代码显式调用 suspendCancellableCoroutine
。因为您正在使用一些预先存在的阻塞 IO API,协程会阻塞其调用线程。
要真正利用可挂起代码的强大功能,您必须使用非阻塞 IO API,它可以让您发出请求并提供 API 将在结果为 API 时调用的回调准备好。例如:
NonBlockingHttp.sendRequest("https://example.org/document",
onSuccess = { println("Received document $it") },
onFailure = { Log.e("Failed to fetch the document", it) }
)
有了这种API,无论您是否使用协程,都不会阻塞任何线程。然而,与阻塞 API 相比,它的用法相当笨拙和混乱。这就是协程可以帮助您的:它们允许您以完全相同的形式继续编写代码,就好像它是阻塞的,只是它不是。要获得它,您必须首先编写一个 suspend fun
将您拥有的 API 转换为协程挂起:
suspend fun fetchDocument(url: String): String = suspendCancellableCoroutine { cont ->
NonBlockingHttp.sendRequest(url,
onSuccess = { cont.resume(it) },
onFailure = { cont.resumeWithException(it) }
)
}
现在你的调用代码回到这个:
try {
val document = fetchDocument("https://example.org/document")
println("Received document $document")
} catch (e: Exception) {
Log.e("Failed to fetch the document", e)
}
相反,如果您可以保留阻塞网络 IO,这意味着每个并发网络调用都需要一个专用线程,那么如果没有协程,您将不得不使用异步任务之类的东西,Anko 的 bg
等。这些方法还要求您提供回调,因此协程可以再次帮助您保持自然的编程模型。核心协程库已经包含了你需要的所有部分:
- 一个专门的弹性线程池,如果当前所有线程都被阻塞,它总是启动一个新线程(可通过
Dispatchers.IO
访问) withContext
原语,它允许您的协程从一个线程跳转到另一个线程然后返回
使用这些工具,您可以简单地编写
try {
val document = withContext(Dispatchers.IO) {
JSoup.connect("https://example.org/document").get()
}
println("Received document $it")
} catch (e: Exception) {
Log.e("Failed to fetch the document")
}
当您的协程到达 JSoup 调用时,它将释放 UI 线程并在 IO 线程池中的线程上执行此行。当它解除阻塞并得到结果时,协程将跳回到 UI 线程。