ventana net mostrar modal formulario form entre ejemplo diferencia asp c# winforms async-await

c# - net - ¿Cómo se debe manejar una tarea asincrónica y mostrar un formulario modal en el mismo método?



ventana modal c# asp net (2)

Tengo una aplicación de formularios de Windows en la que envío un correo electrónico usando SmtpClient. Otras operaciones de sincronización en la aplicación utilizan async / await, y me gustaría ser consistente al enviar el correo.

Cuando abro el correo visualizo un cuadro de diálogo modal con un botón de cancelar, y al combinar SendMailAsync con form.ShowDialog es donde las cosas se complican porque se espera que el envío se bloquee, y también ShowDialog. Mi enfoque actual es el siguiente, pero parece desordenado, ¿hay un mejor enfoque para esto?

private async Task SendTestEmail() { // Prepare message, client, and form with cancel button using (Message message = ...) { SmtpClient client = ... CancelSendForm form = ... // Have the form button cancel async sends and // the client completion close the form form.CancelBtn.Click += (s, a) => { client.SendAsyncCancel(); }; client.SendCompleted += (o, e) => { form.Close(); }; // Try to send the mail try { Task task = client.SendMailAsync(message); form.ShowDialog(); await task; // Probably redundant MessageBox.Show("Test mail sent", "Success"); } catch (Exception ex) { string text = string.Format( "Error sending test mail:/n{0}", ex.Message); MessageBox.Show(text, "Error"); } }


Un SendTestEmail implementación existente de SendTestEmail es que, de hecho, es sincrónico, a pesar de que devuelve una Task . Por lo tanto, solo regresa cuando la tarea ya se ha completado, porque ShowDialog es sincrónico (naturalmente, porque el diálogo es modal).

Esto puede ser algo engañoso. Por ejemplo, el siguiente código no funcionaría de la manera esperada:

var sw = new Stopwatch(); sw.Start(); var task = SendTestEmail(); while (!task.IsCompleted) { await WhenAny(Task.Delay(500), task); StatusBar.Text = "Lapse, ms: " + sw.ElapsedMilliseconds; } await task;

Se puede abordar fácilmente con Task.Yield , que permitiría continuar de forma asíncrona en el nuevo bucle de mensaje de diálogo modal (anidado):

public static class FormExt { public static async Task<DialogResult> ShowDialogAsync( Form @this, CancellationToken token = default(CancellationToken)) { await Task.Yield(); using (token.Register(() => @this.Close(), useSynchronizationContext: true)) { return @this.ShowDialog(); } } }

Entonces podrías hacer algo como esto (no probado):

private async Task SendTestEmail(CancellationToken token) { // Prepare message, client, and form with cancel button using (Message message = ...) { SmtpClient client = ... CancelSendForm form = ... // Try to send the mail var ctsDialog = CancellationTokenSource.CreateLinkedTokenSource(token); var ctsSend = CancellationTokenSource.CreateLinkedTokenSource(token); var dialogTask = form.ShowDialogAsync(ctsDialog.Token); var emailTask = client.SendMailExAsync(message, ctsSend.Token); var whichTask = await Task.WhenAny(emailTask, dialogTask); if (whichTask == emailTask) { ctsDialog.Cancel(); } else { ctsSend.Cancel(); } await Task.WhenAll(emailTask, dialogTask); } } public static class SmtpClientEx { public static async Task SendMailExAsync( SmtpClient @this, MailMessage message, CancellationToken token = default(CancellationToken)) { using (token.Register(() => @this.SendAsyncCancel(), useSynchronizationContext: false)) { await @this.SendMailAsync(message); } } }


Consideraría manejar el evento Form.Shown y enviar el correo electrónico desde allí. Como se ShowDialog forma asíncrona, no tendrá que preocuparse por "evitar la naturaleza de bloqueo de ShowDialog ", y tendrá una forma ligeramente más limpia de sincronizar el cierre del formulario y mostrar el mensaje de éxito o error.

form.Shown += async (s, a) => { try { await client.SendMailAsync(message); form.Close(); MessageBox.Show("Test mail sent", "Success"); } catch(Exception ex) { form.Close(); string text = string.Format( "Error sending test mail:/n{0}", ex.Message); MessageBox.Show(text, "Error"); } }; form.ShowDialog();