在C#中实现函数非阻塞/异步的简单方法?

在C#中实现函数非阻塞/异步的简单方法?,c#,.net,asynchronous,task-parallel-library,async-await,C#,.net,Asynchronous,Task Parallel Library,Async Await,我有一个按钮,当用户点击它时,它会发送一封电子邮件。我希望这只是立即返回并在后台发送电子邮件,而不会在处理电子邮件时挂起UI。我搜索了async/await/等等,发现了很多不同的方法——我正在寻找一个简单的解决方案。我的电子邮件代码: public void SendEmail(string toAddress, string subject, string body, string code = null) { try { var fromAddressObj =

我有一个按钮,当用户点击它时,它会发送一封电子邮件。我希望这只是立即返回并在后台发送电子邮件,而不会在处理电子邮件时挂起UI。我搜索了
async
/
await
/等等,发现了很多不同的方法——我正在寻找一个简单的解决方案。我的电子邮件代码:

public void SendEmail(string toAddress, string subject, string body, string code = null) {
    try {

        var fromAddressObj = new MailAddress("noreply@me.com", "Name");
        var toAddressObj = new MailAddress(toAddress, toAddress);
        const string fromPassword = "Password";

        var smtp = new SmtpClient {
            Host = "smtp.office365.com",
            Port = 587,
            EnableSsl = true,
            DeliveryMethod = SmtpDeliveryMethod.Network,
            UseDefaultCredentials = false,
            Credentials = new NetworkCredential(fromAddressObj.Address, fromPassword)
        };
        using (var message = new MailMessage(fromAddressObj, toAddressObj) {
            Subject = subject,
            IsBodyHtml = true

        }) {
            message.Body = body;
            smtp.Send(message);
        }

    } catch (Exception e) {
        Elmah.ErrorSignal.FromCurrentContext().Raise(e);
    }
}

如何修改此方法以使调用方不被阻止?

您可以在任务中运行方法,而不必等待它:

private void button_Click(object sender, EventArgs e)
{
   Task.Run( () => SendEMail(address, subject, body) );
}

SmtpClient
具有
SendMailAsync
,因此使用它而不是
Send
将在发送时取消阻止UI线程,但您仍然可以处理可能发生的任何异常:

private async void button_Click(object sender, EventArgs e)
{
   await SendEmailAsync(address, subject, body)
}

public async Task SendEmailAsync(string toAddress, string subject, string body, string code = null) 
{
    try 
    {
        var fromAddressObj = new MailAddress("noreply@me.com", "Name");
        var toAddressObj = new MailAddress(toAddress, toAddress);
        const string fromPassword = "Password";

        var smtp = new SmtpClient {
            Host = "smtp.office365.com",
            Port = 587,
            EnableSsl = true,
            DeliveryMethod = SmtpDeliveryMethod.Network,
            UseDefaultCredentials = false,
            Credentials = new NetworkCredential(fromAddressObj.Address, fromPassword)
        };
        using (var message = new MailMessage(fromAddressObj, toAddressObj)
        {
            Subject = subject,
            IsBodyHtml = true
        }) 
        {
            message.Body = body;
            await smtp.SendMailAsync(message);
        }
    }
    catch (Exception e) 
    {
        Elmah.ErrorSignal.FromCurrentContext().Raise(e);
    }
}
如果
async
方法的同步部分(wait之前的部分)仍然能够阻止UI线程,则可以将其卸载到
线程池
线程:

private async void button_Click(object sender, EventArgs e)
{
   await Task.Run(() => SendEmailAsync(address, subject, body))
}

注:

  • 除非您处于无法使用
    wait
    (UI事件处理程序不属于此类情况)的情况,否则不要在未等待的情况下启动
    任务
  • async void
    仅适用于UI事件处理程序。不要在其他地方使用它

不要使用
任务。当您有
异步
版本的操作时,运行
。这会浪费线程并限制可伸缩性。看看@SB2055,理想情况下,您还应该支持取消,这是SmtpClient.SendMailAsync不支持的。您可能想从中查看
SendMailExImplAsync
。无需执行
任务。运行
SmtpClient
具有自然异步的API。
Task.Run
用于CPU限制的操作。这是一个IO绑定的操作。正如@YuvalItzchakov所提到的,
SmtpClient
已经有了一个
SendMailAsync
操作。虽然这样做有效,但这并不是最好的解决方案。你可以自己回答并建议一个版本,在这个版本中,你不阻塞用户界面,它可以立即继续吗?这里的另一个答案不满足OPs请求“呼叫者未被阻止”。它通过等待来阻止语句的进一步执行。这就是等待某事的意义。@nvoigt另一个答案不阻塞(这就是等待某事的意义)。请您解释一下为什么选择的答案是错误的,好吗?假设没有异步版本的操作,我仍然需要在不阻塞的情况下运行它,那么我显然不能使用wait,对吗?所以在某些情况下,使用任务而不等待似乎是可取的?为我的幼稚道歉,任何解释都会有帮助。@SB2055如果没有异步版本(
async
或其他版本),那么
Task.Run
将非常合适。但是您仍然有一个
任务
等待
,这就是
任务返回的任务。你需要一个很好的理由不等待一项任务,而你的情况却没有。我觉得告诉OP他需要什么是很荒谬的。他决定他认为必要的事情。如果他不想等待功能,告诉他必须等待并不能回答他的问题。你的回答在技术上很好。。。但这并不是这个问题的答案。@nvoigt不,OP需要的是操作将是:“非阻止”和“在处理电子邮件时立即返回并在后台发送电子邮件,而不占用UI”。这只需使用
异步等待
即可实现。没有理由不等待返回的任务,这样做是不好的。如果出于某种原因OP真的不能使用
wait
,那么至少添加一个continuation,使用
continuateWith
@nvoigt处理任何问题。这不是民主,你不应该删除你的答案,因为它被否决了,就像我没有删除我的答案,因为你的答案被否决了或者被否决了一样。如果你认为答案是正确的,你应该保留你的答案。当你意识到它错了时,你应该删除它。