我正在Kotlin中为Android编写一个简单的演示应用程序,该应用程序使用Jsoup检索网页的标题。我正在使用Dispatchers.Main
作为上下文进行网络呼叫。
我对协程的理解是,如果我在launch
上调用Dispatchers.Main
,它会在主线程上运行,但会暂停执行以免阻塞线程
我对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。
答案 0 :(得分:3)
我对协程的理解是,如果我在Dispatchers.Main上调用launch,它确实在主线程上运行,但是会暂停执行以免阻塞线程。
挂起执行以便不阻塞线程的唯一要点是标记为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
}
答案 1 :(得分:0)
协程暂停不是神奇地“解除阻止”现有阻止网络呼叫的功能。严格来说,这是 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的{{1 }}等。这些方法还需要您提供回调,因此协程可以再次帮助您保持自然的编程模型。协程库的核心已经包含了您需要的所有部分:
bg
访问),它将始终启动一个新线程Dispatchers.IO
原语,它使协程可以从一个线程跳转到另一个线程,然后返回使用这些工具,您可以轻松编写
withContext
当协程到达JSoup调用时,它将设置UI线程空闲,并在IO线程池中的线程上执行此行。解除阻塞并获得结果后,协程将跳回UI线程。