One dubious thing about your existing SendTestEmail implementation is that it is actually synchronous even though it returns a Task . Thus, it returns only when the task is already completed, because ShowDialog is synchronous (naturally, because the dialog is modal).
This can be somewhat misleading. For example, the following code will not work as expected:
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;
It can be easily eliminated using Task.Yield , which allows you to continue asynchronously in a new (nested) modal message dialog:
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(); } } }
Then you can do something like this (untested):
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); } } }
Noseratio
source share