thread second run method hilos form español await async c# asynchronous xamarin async-await task

c# - second - Entendiendo async/await y Task.Run()



xamarin forms background task (4)

Pensé que entendía async / Task.Run() y Task.Run() bastante bien hasta que encontré este problema:

Estoy programando una aplicación Xamarin.Android usando un RecyclerView con un ViewAdapter . En mi método OnBindViewHolder , intenté asincronizar algunas imágenes

public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position) { // Some logic here Task.Run(() => LoadImage(postInfo, holder, imageView).ConfigureAwait(false)); }

Luego, en mi función LoadImage hice algo como:

private async Task LoadImage(PostInfo postInfo, RecyclerView.ViewHolder holder, ImageView imageView) { var image = await loadImageAsync((Guid)postInfo.User.AvatarID, EImageSize.Small).ConfigureAwait(false); var byteArray = await image.ReadAsByteArrayAsync().ConfigureAwait(false); if(byteArray.Length == 0) { return; } var bitmap = await GetBitmapAsync(byteArray).ConfigureAwait(false); imageView.SetImageBitmap(bitmap); postInfo.User.AvatarImage = bitmap; }

Ese trozo de código funcionó . ¿Pero por qué?

Lo que he aprendido, después de que configure await se establece en falso, el código no se ejecuta en SynchronizationContext (que es el subproceso de la interfaz de usuario).

Si hago el método OnBindViewHolder async y uso await en lugar de Task.Run, el código se bloquea en

imageView.SetImageBitmap(bitmap);

Decir que no está en el hilo de la interfaz de usuario, lo que tiene mucho sentido para mí.

Entonces, ¿por qué el código de async / await se bloquea mientras que Task.Run () no lo hace?

Actualización: respuesta

Dado que no se esperó la Tarea de Ejecución, no se mostró la excepción lanzada. Si esperaba la tarea. Ejecutar, se produjo el error que esperaba. Más explicaciones se encuentran en las respuestas a continuación.


Es tan simple como no esperar el Task.Run, por lo que la excepción se come y no se devuelve al sitio de llamada de Task.Run.

Agregue "await" delante de Task.Run y ​​obtendrá la excepción.

Esto no bloqueará su aplicación:

private void button1_Click(object sender, EventArgs e) { Task.Run(() => { throw new Exception("Hello");}); }

Sin embargo, esto bloqueará su aplicación:

private async void button1_Click(object sender, EventArgs e) { await Task.Run(() => { throw new Exception("Hello");}); }


Probablemente el acceso a la interfaz de usuario aún arroja UIKitThreadAccessException . No lo observa porque no utiliza la palabra clave Task.Wait() o Task.Wait() en un marcador que Task.Run() . Consulte Detectar una excepción lanzada por una discusión del método asíncrono en , la documentación de MSDN sobre el tema está un poco anticuada.

Puede adjuntar continuación al marcador que Task.Run() e inspeccionar las excepciones lanzadas dentro de una acción aprobada:

Task marker = Task.Run(() => ...); marker.ContinueWith(m => { if (!m.IsFaulted) return; // Marker Faulted status indicates unhandled exceptions. Observe them. AggregateException e = m.Exception; });

En general, el acceso a la UI desde un subproceso que no es UI puede hacer que una aplicación sea inestable o que falle, pero no está garantizado.

Para obtener más información, consulte Cómo manejar las excepciones de Task.Run , Android - Problema con las tareas asíncronas en , el significado del artículo TaskStatus de Stephen Toub y el artículo Trabajando con el UI Thread en Microsoft Docs.


Task.Run() y el subproceso de la interfaz de usuario deben utilizarse para un propósito diferente:

  • Task.Run() debe usarse para métodos vinculados a la CPU .
  • UI-Thread se debe utilizar para los métodos relacionados con la interfaz de usuario .

Al mover su código a Task.Run() , evita que se bloquee el hilo de la interfaz de usuario. Esto puede resolver su problema, pero no es la mejor práctica porque es malo para su desempeño. Task.Run() bloquea un hilo en el grupo de hilos.

Lo que deberías hacer en cambio es llamar a tu método relacionado con la IU en el hilo de la IU. En Xamarin, puedes ejecutar cosas en el hilo de la IU usando Device.BeginInvokeOnMainThread() :

// async is only needed if you need to run asynchronous code on the UI thread Device.BeginInvokeOnMainThread(async () => { await LoadImage(postInfo, holder, imageView).ConfigureAwait(false) });

La razón por la que funciona, incluso si no lo llama explícitamente en el subproceso de la interfaz de usuario, probablemente se deba a que Xamarin, de alguna manera, detecta que es algo que debería ejecutarse en el subproceso de la interfaz de usuario y cambia este trabajo al subproceso de la interfaz de usuario.

Aquí hay algunos artículos útiles de Stephen Cleary que me ayudaron a escribir esta respuesta y que te ayudarán a entender mejor el código asíncrono:

https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html


Task.Run está Task.Run cola LoadImage para ejecutar el proceso asíncrono en el grupo de subprocesos con ConfigureAwait(false) . Sin LoadImage tarea que LoadImage está devolviendo NO se espera y creo que esa es la parte importante aquí.

Así que el resultado de Task.Run es que inmediatamente devuelve una Task<Task> , pero la tarea externa no tiene ConfigureAwait(false) , así que todo se resuelve en el hilo principal.

Si cambias tu código a

Task.Run(async () => await LoadImage(postInfo, holder, imageView).ConfigureAwait(false));

Estoy esperando que llegue el error de que el hilo no se está ejecutando en el hilo de la interfaz de usuario.