在Kotlin中,如何使暂停调用“等待”内部的阻塞线程?

时间:2019-10-03 11:22:54

标签: kotlin coroutine

让我更好地解释一下...

我具有暂停功能

suspend fun foo(){
   if(startFlag){
     myMethod()
    }
}

当我第一次调用myMethod()时,我将检查startFlag的值,如果值为false,则会调用myMethod。

我不能为此使用init {},必须是我第一次调用foo()时(由于逻辑更复杂)。

我的问题:

我第一次打电话给foo()时会打电话给myMethod()(让我们说要解决很长时间)

然后再次调用foo(),我想检测到myMethod()已经在线程中运行,我应该等待它。

但是由于fun foo()本身已被暂停,因此不确定是否可以这样做。

有什么想法吗?

3 个答案:

答案 0 :(得分:4)

这是一个有趣的问题,我想分享一种解决方法,希望这会有所帮助。

基本解决方案

您可以使用myMethod()恢复Deferred计算。基本上,代码看起来像这样:

    val myMethodDeferred = GlobalScope.async {
        myMethod()
    }

    suspend fun foo(){
        myMethodDeferred.await()
    }

async函数返回一个保存Deferred计算的myMethod对象。当您调用myMethodDeferred.await()时,它将等待计算结束并返回myMethod的结果(如果需要,请使用该结果)。

如果只希望在调用foo()时执行计算,则可以像这样将参数添加到async调用中:async(start = CoroutineStart.LAZY){ ...。它将导致计算延迟从第一个.await()调用开始。

({GlobalScope可以替换为任何CoroutineScope,或者替换为作为构造函数参数传递给类的范围。如果myMethod()不是挂起函数而是阻塞函数如您所描述的,您可能希望使用适当的CoroutineScope,例如CoroutineScope(Dispatchers.IO)(如果它执行I / O计算。)

处理失败

该解决方案的缺点是myMethod()失败并发生异常。如果myMethod()失败,则该失败将存储在延迟中,并且对foo()的每次调用也会失败,而不是尝试再次运行myMethod()

要处理myMethod()的失败,我建议声明一个名称为Retryable的类,该类将保存可以重试的计算,与Deferred的保存方法类似。代码:

class Retryable<T>(private val computation: suspend CoroutineScope.() -> T) {

    private var deferred: Deferred<T>? = null

    suspend fun await(): T {
        val oldDeferred = deferred
        val needsRetry = oldDeferred == null || oldDeferred.isCompleted && oldDeferred.getCompletionExceptionOrNull() != null
        if (needsRetry) {
            deferred = GlobalScope.async(block = computation)
        }
        return deferred!!.await()
    }

}

用法应如下所示:

    val myMethodRetryable = Retryable { myMethod() }

    suspend fun foo(){
        myMethodRetryable.await()
    }

现在,每次对foo()的调用都将等待myMethod()的计算完成,但是如果已经以失败结束,它将重新运行它并等待新的计算。 / p>

(这里,CoroutineScope应该作为参数传递给Retryable,但我不想使示例代码复杂化。)


替代解决方案:使用异步工厂功能

由于myMethod()仅应调用一次,因此,如果不能在init {}上调用它,则可以在工厂函数上调用它。

示例代码:

class MyClass(private val myMethodResult: String) {
  companion object {
    suspend fun create(): MyClass {
        val myMethodResult = myMethod()
        return MyClass(myMethodResult)
     }
     private suspend fun myMethod(): String {
        ...
     }
  }
  ...
}

如果您可以在使用前从挂起函数初始化MyClass一次,则很有用。

(如果myMethod()不是挂起的函数,而是您所描述的阻塞函数,则可能需要用适当的上下文包装它,例如,withContext(Dispatchers.IO) { myMethod() }如果进行I / O计算。)

答案 1 :(得分:1)

您可以只使用Semaphore


...

suspend fun foo(semaphore: Semaphore) = semaphore.withPermit {

    if (startFlag) {
        myMethod()
    }
}
...

然后创建Semaphore并以1个许可作为参数传递。

val myJobSemaphore = Semaphore(1)

答案 2 :(得分:-1)

如果我没看错,可以使用Mutex来完成,例如:

var startFlag = true
val mutex = Mutex()

suspend fun foo() = mutex.withLock {
    if (startFlag) {
        myMethod()
        startFlag = false
    }
}

这是指向playground

的链接