Acceder a los controles de UI en Task.Run con async / await en WinForms

Tengo el siguiente código en una aplicación de WinForms con un botón y una etiqueta:

using System; using System.IO; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private async void button1_Click(object sender, EventArgs e) { await Run(); } private async Task Run() { await Task.Run(async () => { await File.AppendText("temp.dat").WriteAsync("a"); label1.Text = "test"; }); } } } 

Esta es una versión simplificada de la aplicación real en la que estoy trabajando. Tenía la impresión de que al usar async / Task.Run en mi Task.Run podría establecer la propiedad label1.Text . Sin embargo, al ejecutar este código me sale el error de que no estoy en el hilo de la interfaz de usuario y no puedo acceder al control.

¿Por qué no puedo acceder al control de etiqueta?

Cuando utilizas Task.Run() , estás seguro de que no quieres que el código se ejecute en el contexto actual, así que eso es exactamente lo que sucede.

Pero no es necesario usar Task.Run() en su código. Los métodos async correctamente escritos no bloquearán el hilo actual, por lo que puede usarlos directamente desde el hilo de la interfaz de usuario. Si lo hace, await se asegurará de que el método se reanude en el hilo de UI.

Esto significa que si escribes tu código así, funcionará:

 private async void button1_Click(object sender, EventArgs e) { await Run(); } private async Task Run() { await File.AppendText("temp.dat").WriteAsync("a"); label1.Text = "test"; } 

Prueba esto

 private async Task Run() { await Task.Run(async () => { await File.AppendText("temp.dat").WriteAsync("a"); }); label1.Text = "test"; } 

O

 private async Task Run() { await File.AppendText("temp.dat").WriteAsync("a"); label1.Text = "test"; } 

O

 private async Task Run() { var task = Task.Run(async () => { await File.AppendText("temp.dat").WriteAsync("a"); }); var continuation = task.ContinueWith(antecedent=> label1.Text = "test",TaskScheduler.FromCurrentSynchronizationContext()); await task;//I think await here is redundant } 

async / await no garantiza que se ejecutará en el subproceso UI. await capturará el SynchronizationContext actual y continuará la ejecución con el contexto capturado una vez que la tarea se haya completado.

Entonces, en su caso, tiene un Task.Run nested que está dentro de Task.Run por Task.Run tanto, second Task.Run capturará el contexto que no va a ser UiSynchronizationContext porque lo está ejecutando WorkerThread desde ThreadPool .

¿Responde esto a tu pregunta?

Prueba esto:

reemplazar

 label1.Text = "test"; 

con

 SetLabel1Text("test"); 

y agrega lo siguiente a tu clase:

 private void SetLabel1Text(string text) { if (InvokeRequired) { Invoke((Action)SetLabel1Text, text); return; } label1.Text = text; } 

InvokeRequired devuelve verdadero si NO está en el hilo de UI. El método Invoke () toma el delegado y los parámetros, cambia al subproceso UI y luego llama al método recursivamente. Usted regresa después de la llamada a Invoke () porque el método ya ha sido llamado recursivamente antes de la devolución de Invoke (). Si se encuentra en el hilo de la interfaz de usuario cuando se llama al método, InvokeRequired es falso y la asignación se realiza directamente.

¿Por qué usas Task.Run? que inicia un nuevo hilo de trabajo (CPU encuadernado) y causa su problema.

probablemente deberías hacer eso:

  private async Task Run() { await File.AppendText("temp.dat").WriteAsync("a"); label1.Text = "test"; } 

aguarde, asegúrese de continuar en el mismo contexto, excepto si usa .ConfigureAwait (false);

Porque está en un hilo diferente y las llamadas entre hilos no están permitidas.

Deberá pasar el “contexto” al hilo que está iniciando. Vea un ejemplo aquí: http://reedcopsey.com/2009/11/17/synchronizing-net-4-tasks-with-the-ui-thread/

Voy a darle mi última respuesta que he dado para la comprensión asíncrona.

La solución es como usted sabe que cuando llama al método asíncrono necesita ejecutarlo como una tarea.

Aquí hay un código de aplicación de consola rápido que puede usar para su referencia, le facilitará la comprensión del concepto.

 using System; using System.Threading; using System.Threading.Tasks; public class Program { public static void Main() { Console.WriteLine("Starting Send Mail Async Task"); Task task = new Task(SendMessage); task.Start(); Console.WriteLine("Update Database"); UpdateDatabase(); while (true) { // dummy wait for background send mail. if (task.Status == TaskStatus.RanToCompletion) { break; } } } public static async void SendMessage() { // Calls to TaskOfTResult_MethodAsync Task returnedTaskTResult = MailSenderAsync(); bool result = await returnedTaskTResult; if (result) { UpdateDatabase(); } Console.WriteLine("Mail Sent!"); } private static void UpdateDatabase() { for (var i = 1; i < 1000; i++) ; Console.WriteLine("Database Updated!"); } private static async Task MailSenderAsync() { Console.WriteLine("Send Mail Start."); for (var i = 1; i < 1000000000; i++) ; return true; } } 

Aquí estoy tratando de iniciar una tarea llamada enviar correo. Interino Quiero actualizar la base de datos, mientras que el fondo está realizando la tarea de enviar correo.

Una vez que se ha realizado la actualización de la base de datos, está esperando a que se complete la tarea de envío de correo. Sin embargo, con este enfoque, es bastante claro que puedo ejecutar tareas en segundo plano y continuar con el hilo original (principal).