Wie rufe ich eine asynchrone Anfrage synchron mit einem Timeout-Wert auf?

Jan 07 2021

Ich muss synchron eine asynchrone API-Anfrage aufrufen. Da die Beantwortung dieser API-Anfrage lange dauert, möchte ich auch eine Zeitüberschreitung festlegen, um die API-Anfrage fehlzuschlagen und mit null fortzufahren. Hier ist mein Code, um diese API aufzurufen:

private suspend fun call(
    accessStage: AccessStage,
): Response? = withContext<Response?>(Dispatchers.IO) {

    return@withContext withTimeoutOrNull(1000) {

        suspendCoroutine<Response?> { continuation ->

            val request = External3rdPartyApi.newRequest(
                accessStage
            ) { response, throwable ->

                continuation.resume(response)
            }

            request.parameters = hashMapOf<String, String>().apply {
                put["token"] = External3rdPartyApi.TOKEN
                put["salt"] = External3rdPartyApi.calculateSalt(accessStage)
            }
            request.executeAsync()
        }
    }
}

Ich kann nicht ändern, wie es External3rdPartyApifunktioniert.

Ich denke, der obige Code sieht böse aus. Außerdem habe ich in einer anderen Antwort gelesen :

withTimeout { ... }ist so konzipiert, dass der laufende Vorgang bei Zeitüberschreitung abgebrochen wird. Dies ist nur möglich, wenn der betreffende Vorgang abgebrochen werden kann .

Also sollte ich suspendCancellableCoroutinestatt verwenden suspendCoroutine?

Wie kann ich es besser schreiben?

Antworten

1 ChristianB Jan 07 2021 at 19:42

Die Verwendung suspendCoroutineist in Ordnung, wenn Sie die Stornierung der Coroutine nicht handhaben können (oder wollen). Da Sie jedoch eine Zeitüberschreitung haben, sollten Sie die Verwendung von suspendCancellableCoroutine in Betracht ziehen und das Abbruchereignis behandeln, um die Arbeit zu stoppen (in der Drittanbieterfunktion - wenn Sie können).

suspendCancellableCoroutine<T> { continuation ->
  continuation.invokeOnCancellation { throwable ->
    // now you could stop your (third party) work  
  }        
}

Wenn Ihre Drittanbieterfunktion eine Ausnahme auslöst, können Sie versuchen, sie abzufangen und Ihre Fortsetzung zu beenden, indem Sie entweder mit dem Wert fortfahren exceptionoder einen nullWert zurückgeben (abhängig von Ihrem Anwendungsfall):

suspendCancellableCoroutine<T?> { continuation ->
  try {
    continuation.resume(thirdParty.call())
  } catch (e: Exception) {
    // resume with exception
    continuation.resumeWithException(e)
    // or just return null and swallow the exception
    continuation.resume(null)
  }   
}

Lasst uns alles zusammenfügen

suspend fun call(): Response? = withContext(Dispatchers.IO) {
  return@withContext withTimeoutOrNull(1000L) {
    return@withTimeoutOrNull suspendCancellableCoroutine { continuation ->
      try {
        continuation.resume(External3rdPartyApi.newRequest(accessStage))
      } catch (e: Exception) {
        // resume with exception
        continuation.resumeWithException(e)
        
        // or just return null and swallow the exception
        continuation.resume(null)
      }
         
      // in case the coroutine gets cancelled - because of timeout or something else
      continuation.invokeOnCancellation {
        // stop all the work
      }
    }
  }
}