¿Por qué Task.WaitAll () no bloquea o provoca un interlocking aquí?

En el siguiente ejemplo se utilizan dos llamadas de await . Para obtener un rendimiento, la muestra se convierte en Task.WaitAll() lugar (no es realmente más rápido, pero esto es solo un ejemplo).

Este es el código de una biblioteca que usa Sqlite.Net en Android y el método se llama desde OnResume() en el hilo principal de la interfaz de usuario:

 public async Task SetupDatabaseAsync() { await CreateTableAsync(); await CreateTableAsync(); } 

Aquí está la alternativa:

 public void SetupDatabaseAsync() { var t1 = CreateTableAsync(); var t2 = CreateTableAsync(); Task.WaitAll(t1, t2); } 

Pero desde mi entendimiento, Task.WaitAll() debería bloquear el hilo de la interfaz de usuario mientras espera, lo que lleva a un interlocking. Pero funciona bien. ¿Es porque las dos llamadas en realidad no invocan nada en el hilo de la interfaz de usuario?

¿Cuál es la diferencia si uso Task.WhenAll() lugar? Supongo que funcionaría incluso si se invocara el subproceso de la interfaz de usuario, al igual que con await .

Describo los detalles de la situación de punto muerto en mi blog. También tengo un artículo de MSDN sobre SynchronizationContext que puede ser útil.

En resumen, Task.WaitAll se interbloqueará en su escenario, pero solo si las tareas necesitan sincronizarse nuevamente con el subproceso de la interfaz de usuario para completar. Puede concluir que CreateTableAsync() no se sincroniza de nuevo con el subproceso de la interfaz de usuario.

En contraste, este código se bloqueará:

 public async Task SetupDatabaseAsync() { await CreateTableAsync(); await CreateTableAsync(); } Task.WaitAll(SetupDatabaseAsync()); 

Te recomiendo que no bloquees en código asíncrono; en el mundo async , la sincronización con el contexto es el comportamiento predeterminado (como describo en mi introducción async ), por lo que es fácil hacerlo accidentalmente. Algunos cambios en Sqlite.Net en el futuro pueden (accidentalmente) volver a sincronizarse con el contexto original, y luego cualquier código que use Task.WaitAll como su ejemplo original se Task.WaitAll repentinamente.

Es mejor usar async “hasta el final”:

 public Task SetupDatabaseAsync() { var t1 = CreateTableAsync(); var t2 = CreateTableAsync(); return Task.WhenAll(t1, t2); } 

“Async todo el camino” es una de las pautas que recomiendo en mi artículo sobre prácticas recomendadas asíncronas .

Cuando está bloqueando el subproceso de la interfaz de usuario (y el contexto de sincronización actual) solo causará un interlocking si una de las tareas que está esperando en los comisionados es un delegado al contexto actual y luego lo espera (de forma síncrona o asíncrona). El locking sincrónico en cualquier método asíncrono no es un punto muerto instantáneo en todos los casos.

Debido a que los métodos async , de manera predeterminada, combinan el rest del método con el contexto de sincronización actual y después de cada await , y debido a que la tarea nunca terminará hasta que eso suceda, eso significa que esperar sincrónicamente en los métodos que usan async/await a menudo punto muerto; al menos a menos que el comportamiento descrito se invalide explícitamente (a través de, por ejemplo, ConfigureAwait(false) ).

Usar WhenAll significa que no estás bloqueando el contexto de sincronización actual. En lugar de bloquear el subproceso, solo está progtwigndo otra continuación para que se ejecute cuando todas las demás tareas finalicen, dejando el contexto libre para manejar cualquier otra solicitud que esté lista ahora (como, por ejemplo, la continuación del método async subyacente que WhenAll está esperando).

Quizás esta muestra demuestre lo que podría estar pasando. Es una vista de iOS cargando. Pruébelo con la llamada de espera y sin ella (comentado más abajo). Sin ninguna espera en la función, se ejecutará de forma síncrona y se bloqueará la interfaz de usuario.

  public async override void ViewDidLoad() { base.ViewDidLoad (); var d1 = Task.Delay (10); var d2 = Task.Delay (10000); //await Task.Delay (10); Task.WaitAll (d1, d2); this.label.Text = "Tasks have ended - really!"; } public override void ViewWillAppear(bool animated) { base.ViewWillAppear (animated); this.label.Text = "Tasks have ended - or have they?"; }