Prefacio : Estoy buscando una explicación, no solo una solución. Ya conozco la solución.
A pesar de haber pasado varios días estudiando artículos de MSDN sobre el patrón asincrónico basado en tareas (TAP), asíncrono y esperar, todavía estoy un poco confundido acerca de algunos de los detalles más finos.
Estoy escribiendo un registrador para las aplicaciones de la Tienda Windows, y quiero admitir el registro asíncrono y sincrónico. Los métodos asincrónicos siguen el TAP, los síncronos deben ocultar todo esto, y verse y funcionar como los métodos ordinarios.
Este es el método principal de registro asincrónico:
private async Task WriteToLogAsync(string text)
{
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file = await folder.CreateFileAsync("log.log",
CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, text,
Windows.Storage.Streams.UnicodeEncoding.Utf8);
}
Ahora el método sincrónico correspondiente ...
Version 1 :
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Wait();
}
Esto parece correcto, pero no funciona. Todo el programa se congela para siempre.
Versión 2 :
Hmm .. Tal vez la tarea no se inició?
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Start();
task.Wait();
}
Esto arroja InvalidOperationException: Start may not be called on a promise-style task.
Versión 3:
Hmm ... Task.RunSynchronouslysuena prometedor.
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.RunSynchronously();
}
Esto arroja InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
Versión 4 (la solución):
private void WriteToLog(string text)
{
var task = Task.Run(async () => { await WriteToLogAsync(text); });
task.Wait();
}
Esto funciona. Entonces, 2 y 3 son las herramientas incorrectas. Pero 1? ¿Qué le pasa a 1 y cuál es la diferencia con 4? ¿Qué hace que 1 cause un congelamiento? ¿Hay algún problema con el objeto de tarea? ¿Hay un punto muerto no obvio?
fuente

Respuestas:
El
awaitinterior de su método asincrónico está tratando de volver al hilo de la interfaz de usuario.Como el subproceso de la interfaz de usuario está ocupado esperando que se complete toda la tarea, tiene un punto muerto.
Mover la llamada asincrónica para
Task.Run()resolver el problema.Debido a que la llamada asíncrona ahora se ejecuta en un subproceso de grupo de subprocesos, no intenta volver al subproceso de la interfaz de usuario y, por lo tanto, todo funciona.
Alternativamente, puedes llamar
StartAsTask().ConfigureAwait(false)antes de esperar la operación interna para que regrese al grupo de subprocesos en lugar del subproceso de la interfaz de usuario, evitando por completo el punto muerto.fuente
ConfigureAwait(false)es la solución adecuada en este caso. Como no tiene necesidad de llamar a las devoluciones de llamada en el contexto capturado, no debería hacerlo. Al ser un método API, debe manejarlo internamente, en lugar de obligar a todas las personas que llaman a salir del contexto de la interfaz de usuario.Microsoft.Bcl.Async.Llamar
asynccódigo desde código síncrono puede ser bastante complicado.Explico las razones completas de este punto muerto en mi blog . En resumen, hay un "contexto" que se guarda de forma predeterminada al comienzo de cada uno
awaity se utiliza para reanudar el método.Entonces, si esto se llama en un contexto de UI, cuando se
awaitcompleta, elasyncmétodo intenta volver a ingresar ese contexto para continuar ejecutándose. Desafortunadamente, el código que usaWait(oResult) bloqueará un hilo en ese contexto, por lo que elasyncmétodo no puede completarse.Las pautas para evitar esto son:
ConfigureAwait(continueOnCapturedContext: false)tanto como sea posible. Esto permite suasyncmétodos continúen ejecutándose sin tener que volver a ingresar al contexto.asynctodo el camino. Use enawaitlugar deResultoWait.Si su método es naturalmente asíncrono, entonces (probablemente) no debería exponer un contenedor sincrónico .
fuente
asynccómo haría esto y evitaría una situación de incendio y olvido.awaitse admite encatchbloques a partir de VS2015. Si tiene una versión anterior, puede asignar la excepción a una variable local y hacer lo siguienteawaitdespués del bloque catch .Aquí esta lo que hice
funciona muy bien y no bloquea el hilo de la interfaz de usuario
fuente
Con un pequeño contexto de sincronización personalizado, la función de sincronización puede esperar la finalización de la función asíncrona, sin crear un punto muerto. Aquí hay un pequeño ejemplo para la aplicación WinForms.
fuente