如何使“不恰当的阻塞方法”调用;合适?

我目前正在尝试更多地利用kotlin协程。但我面临一个问题:当在这些协程中使用moshi或okhttp时,我得到一个警告:

“不恰当的阻塞方法调用”

解决这些问题的最好方法是什么?我真的不想不合适;-)

97426 次浏览

此警告是关于阻塞当前线程和协程的方法不能正确挂起。这样,您就失去了协程的所有好处,并再次降级为每个线程一个任务。

每一种情况都应以不同的方式处理。对于可悬挂的http调用,你可以使用Ktor HTTP客户端。但有时没有针对您的情况的库,因此您可以编写自己的解决方案,或者忽略此警告。

编辑:withContext(Dispatchers.IO)或一些自定义调度程序可以用来解决这个问题。谢谢你的评论。

当调用带有@Throws(IOException::class)注释的挂起函数(Kotlin 1.3.61)时,也会收到此警告。不确定这是否是有意为之。无论如何,你可以通过删除该注释或将其更改为Exception类来抑制此警告。

使用withContext包装中的不恰当的阻塞方法调用; 代码转换为另一个背景

也就是说(例如):

如果你正在执行一个读/写阻塞方法调用:

val objects = withContext(Dispatchers.IO) { dao.getAll() }

如果您正在执行阻塞网络请求(使用Retrofit):

val response = withContext(Dispatchers.IO) { call.execute() }

或者如果你正在执行一个CPU密集型的阻塞任务:

val sortedUsers = withContext(Dispatchers.Default) { users.sortByName() }

这将挂起当前协程,然后在不同的线程(从Dispatchers.IODispatchers.Default池)上执行“不恰当的阻塞呼叫”;,从而不阻塞你的协程正在执行的线程。

如果你选择像一些答案建议的那样压抑,那就使用

@Suppress("BlockingMethodInNonBlockingContext")

我使用的是Android Studio 4.1,当我使用Moshi或操纵File时,会显示警告。将代码包装在withContext中没有帮助,即使我确定我在做什么。

我最近发现,将发出警告的小代码移动到像fun action() {...}这样没有suspend的标准方法中可以消除警告。这很难看,因为它只是隐藏了警告。

更新:从我的个人经验来看,似乎抑制警告或runBlocking更直接。

可能会发生异常,这就是为什么会显示此警告。使用runCatching{}。它捕获从块函数执行中抛出的任何Throwable异常,并将其封装为失败。

例如:

 CoroutineScope(Dispatchers.IO).launch {
runCatching{
makeHttpRequest(URL(downloadLocation))
}
}

我使用dispatchers作为启动参数:

    GlobalScope.launch(Dispatchers.IO) {
// Do background work
        

// Back to main thread
launch(Dispatchers.Main) {
Toast.makeText(context, "SUCCESS!", Toast.LENGTH_LONG)
.show()
}
}

这就是如何挂起协程,在线程中运行阻塞方法,并在结果上恢复它。这也将处理异常,所以你的应用程序不会崩溃。

suspendCoroutine { continuation ->
thread {
try {
doHttpRequest(URL(...)) {
continuation.resume(it)
}
}
catch (t: Throwable) {
continuation.resumeWithException(t)
}
}
}

编辑:预期的方法是使用witchContext(Dispatchers.IO)。我把这个回答留在这里,以防有人发现这种方法有用。

看起来在kotlin.runCatching()中封装调用解决了警告,但不确定为什么…因为正如之前关于runCatching的回答所说,这不是由于抛出异常,因为即使try{} catch也不能解决问题,可能是一些错误检测问题… 我现在使用下面的…

val result = kotlin.runCatching {
OldJavaLib.blockingCallThatThrowsAnException()
}
if (result.isSuccess) {
print("success is on your side")
} else {
print("one failure is never the end")
}

我今天遇到了同样的问题,下面的解决方案对我来说很有效。希望能有所帮助!

        CoroutineScope(Dispatchers.IO).launch {


val call = client.newCall(request)
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
print("Internet access, 4G, Wifi, DNS, etc failed")
}
    

override fun onResponse(call: Call, response: Response) {
if(response.isSuccessful) {
print("Server accepted!")
} else {
print("Server failed!")
}
}
})
}

请记住,这个回调函数只被使用一次。你不能在其他线程中使用它。

一个解决方案是通过suspend fun kotlinx.coroutines.runInterruptible包装阻塞代码。 如果没有它,当协程的作业被取消时,阻塞代码不会被中断

它抑制了编译警告,阻塞代码将在取消时抛出InterruptedException

val job = launch {
runInterruptible(Dispatchers.IO) {
Thread.sleep(500) // example blocking code
}
}


job.cancelAndJoin() // Cause will be 'java.lang.InterruptedException'

https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-interruptible.html

如何将Java阻塞函数转换为可取消的暂停函数?< / >