我有一个Windows窗体应用程序,我使用SmtpClient发送电子邮件。 应用程序中的其他异步操作使用async / await,我希望在发送邮件时保持一致。
我在发送邮件时显示一个带取消按钮的模态对话框,并将SendMailAsync与form.ShowDialog组合在一起,因为等待发送会阻塞,所以ShowDialog也会变得棘手。我目前的方法如下,但看起来很混乱,有没有更好的方法呢?
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");
}
}
答案 0 :(得分:10)
我会考虑处理Form.Shown
事件并从那里发送电子邮件。由于它会异步启动,因此您不必担心会在"周围工作。 ShowDialog
的阻止性质,您可以稍微更清晰地同步关闭表单并显示成功或失败消息。
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();
答案 1 :(得分:1)
关于现有SendTestEmail
实施的一个值得怀疑的事情是它实际上是同步的,尽管它返回Task
。因此,它仅在任务完成时返回,因为ShowDialog
是同步的(当然,因为对话框是模态的)。
这可能有些误导。例如,以下代码无法按预期方式运行:
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;
可以使用Task.Yield
轻松解决,这将允许在新的(嵌套的)模态对话框消息循环上异步继续:
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();
}
}
}
然后你可以做这样的事情(未经测试):
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);
}
}
}