Tengo un bloqueo ocasional en algún código que usa las nuevas funciones de concurrencia de Swift. Este bloqueo nunca parece ocurrir en las compilaciones de desarrollo, ya sea en el simulador o cuando instalo el código en un dispositivo directamente desde Xcode. Sin embargo, sucede con bastante frecuencia cuando la gente instala el código de TestFlight.
El accidente real es este:
Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000040 Exception Codes: 0x0000000000000001, 0x0000000000000040 Thread 6 name: Thread 6 Crashed: 0 libswift_Concurrency.dylib 0x00000001e6df7440 swift::TaskGroup::offer(swift::AsyncTask*, swift::AsyncContext*) + 504 (TaskGroup.cpp:603) 1 libswift_Concurrency.dylib 0x00000001e6df3c5c swift::AsyncTask::completeFuture(swift::AsyncContext*) + 132 (Task.cpp:180) 2 libswift_Concurrency.dylib 0x00000001e6df54d0 completeTaskAndRelease(swift::AsyncContext*, swift::SwiftError*) + 128 (Task.cpp:323) 3 libswift_Concurrency.dylib 0x00000001e6df5425 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1 (Task.cpp:365)No estoy seguro exactamente con qué línea(s) de código corresponde, pero es probable que esté en algún lugar aquí:
class MyClass { var results: [String: Set<String>] = [:] func getSingleResult(index: Int) async -> (String, Set<String>) { // .... } func getAllResults(range: ClosedRange<Int>) async -> [String: Set<String>] { await withTaskGroup( of: (String, Set<String>).self, returning: [String: Set<String>].self ) { [self] group in for i in range { group.addTask { await getSingleResult(index: i) } } var results: [String: Set<String>] = [:] for await result in group { results[result.0] = result.1 } return results } } func blockingDoWork(range: ClosedRange<Int>) { results = [:] let dispatchGroup = DispatchGroup() dispatchGroup.enter() Task.init { results = await getAllResults(range: range) dispatchGroup.leave() } dispatchGroup.wait() // Now do something with `results` } Estoy tratando de cerrar la brecha entre el código sincrónico y asincrónico (quizás de la manera incorrecta). Básicamente, tengo un solo hilo/código síncrono que luego crea un número variable de llamadas asíncronas e intenta bloquear hasta que finalizan todas esas llamadas. Agrega los resultados de esas llamadas en el miembro de la clase de results , que quizás no sea el mejor enfoque, pero fue la única forma en que pude obtener el código asíncrono para comunicarse con el síncrono.
Este código parece funcionar bien en una versión de desarrollo y se ejecuta miles de veces en una versión de lanzamiento, pero luego falla.
Parece que no puedo activar el desinfectante de subprocesos o el desinfectante de direcciones porque mi proyecto Xcode usa Swift Package Manager, y hay un error al usar esos dos que hace que la compilación falle.
¿Alguna idea de lo que podría estar saliendo mal? Supongo que tengo suerte con las compilaciones de desarrollo y que tengo un problema fundamental en este código, pero no sé lo suficiente sobre las sutilezas de las nuevas funciones de simultaneidad de Swift para reconocerlo.
No puede usar semáforos junto con async-await. Ver Concurrencia de Swift: Detrás de escena :
[Primitivos] como los semáforos... no son seguros de usar con la concurrencia de Swift. Esto se debe a que ocultan la información de dependencia del tiempo de ejecución de Swift, pero introducen una dependencia en la ejecución de su código. Dado que el tiempo de ejecución desconoce esta dependencia, no puede tomar las decisiones de programación correctas y resolverlas. En particular, no use primitivos que creen tareas no estructuradas y luego introduzcan retroactivamente una dependencia a través de los límites de la tarea mediante el uso de un semáforo o un primitivo inseguro. Tal patrón de código significa que un subproceso puede bloquearse indefinidamente contra el semáforo hasta que otro subproceso pueda desbloquearlo. Esto viola el contrato de tiempo de ejecución de progreso hacia adelante para subprocesos.
Podría considerar probar con la variable de entorno LIBDISPATCH_COOPERATIVE_POOL_STRICT como se explica aquí , en el mismo video.
Usted pregunta:
Estoy tratando de cerrar la brecha entre el código sincrónico y asincrónico (quizás de la manera incorrecta).
Debe refactorizar el código que llama a este método síncrono para adoptar un patrón asíncrono y luego eliminar todas las API de bloqueo (por ejemplo, wait de semáforo, wait de grupo de despacho, etc.). Esos eran antipatrones en el mundo de GCD y deben evitarse dentro de la concurrencia de Swift. Entiendo por qué los desarrolladores que no están familiarizados con la programación asíncrona se sienten tan atraídos por esos antipatrones síncronos, pero siempre ha sido un error y debería eliminarse del código.
En pocas palabras, en la concurrencia de Swift uno debe "mantener un contrato de tiempo de ejecución que los subprocesos siempre puedan avanzar". Simplemente adopte patrones asincrónicos (es decir, manténgase dentro de async-await sin ninguna técnica de bloqueo de subprocesos de la vieja escuela) y debería estar bien.
FWIW, la concurrencia de Swift: Actualizar una aplicación de muestra muestra técnicas interesantes para actualizar gradualmente una aplicación antigua. Por ejemplo, marque este método de bloqueo como obsoleto, y luego el compilador le advertirá dónde se llama y puede dirigir sus esfuerzos de refactorización a esas rutinas infractoras.