Mezclando código síncrono y asíncrono usando CheckedContinuation
Libranner Santos
29 junio, 20233min de lectura
Es un mecanismo que nos permite crear un puente entre código síncrono y asíncrono.
func withCheckedContinuation<T>(function: String = #function,_ body: (CheckedContinuation<T, Never>) -> Void) async -> T
Esta es la firma de withCheckedContinuation(function:_:)
, veamos los parámetros que requiere:
function
, se utiliza para identificar la función donde se está usando elContinuation
, esto permite identificar elContinuation
para los análisis que se realizan en tiempo de ejecución.body
, este parámetro acepta un closure de tipo(CheckedContinuation<T, Never>) -> Void
. Este será el bloque de código que ejecutaremos de manera síncrona como parte delContinuation
.- Nota que esta función es genérica y puede retornar cualquier tipo.
¿Cómo usar Continuation?
Uno de los usos de Continuation
es para convertir código que usa completion handlers en código compatible con async/await.
A continuación veamos un ejemplo de como podemos usar esta herramienta:
func decodeData(_ encodedData: [EncodedInfo],completion: @escaping ([Info]) -> Void) {// Realiza un procesamiento// Luego retorna un arreglo de objetos Info usando el completion handler.}
Esta función luego de hacer un procesamiento retorna el resultado utilizando un completion handler.
Para poder hacer uso de esta función con async/await
, usamos el siguiente código:
func decodeData(_ encodedData: [EncodedInfo]) async -> [Info] {await withCheckedContinuation { continuation indecodeData(encodedData) { decodedInfo incontinuation.resume(returning: decodedInfo)}}}
Envolvemos el código que usa completion handlers dentro de un withCheckedContinuation
y llamamos la función:
decodeData(_ encodedData: [EncodedInfo],completion: @escaping ([Info]) -> Void)
Por último, utilizamos el continuation
para resumir la tarea y retornar decodedInfo
:
continuation.resume(returning: decodedInfo)
Maneras de resumir un Continuation
Dependiendo del tipo de Continuation
que estemos usando, podemos utilizar los siguientes métodos para reanudar las tareas:
resume(returning:)
se utiliza en el ejemplo y reanuda la ejecución de la tarea devolviendo un valor.resume(throwing:)
reanuda la tarea emitiendo una excepción desde el punto de suspensión.resume(with:)
reanuda la ejecución de la tarea devolviendo unResult<T, E>
lo que significa que puede devolver un valor o un error.resume()
reanuda la ejecución de la tarea sin devolver nada.
Otros Continuations disponibles
Hay otros tipos de Continuation
que tenemos a nuestra disposición:
withUnsafeContinuation(_:)
yUnsafeThrowingContinuation(_:)
: Son alternativas awithCheckedThrowingContinuation(_:)
ywithCheckedContinuation(_:)
, que no realizan ninguna verificación o diagnóstico en tiempo de ejecución. Debemos tener mucho cuidado cuando usamos APIs Unsafe, puesto que queda en nosotros asegurarnos de que el código no presenta fallos. Por ejemplo, asegurarnos de no llamarresume
más de una vez.withCheckedThrowingContinuation(_:)
: Este es similar awithCheckedContinuation(_:)
, pero puede emitir excepciones.
Cosas a tener en cuenta cuando usamos Continuations
Aquí algunas consideraciones importantes al utilizar Continuations:
- El código que se pasa en
body
se ejecuta de manera síncrona dentro de la tarea desde la cual se llama. Una vez que la tarea retorna, se suspende. Por lo tanto, debemos asegurarnos de llamar aresume
utilizando una de las formas disponibles. - Solo debes llamar a uno de los métodos
resume
una vez dentro de una ejecución. - Si no llamas a
resume
, puede haber fugas de memoria y la tarea desde la cual se creó laContinuation
se mantendrá suspendida indefinidamente. - Intentar resumir una
Continuation
más de una vez provocará un error en la ejecución. - A menos que estemos seguros de que el rendimiento se ve significativamente mejorado al usar
UnsafeContinuation
, es recomendable utilizarCheckedContinuation
.