从异步方法调用同步方法

4

我正在为ASP.NET Identity实现EmailServer

我不喜欢async...awaitusing不兼容的情况,因此我的邮件方法是同步的。

那么,我该如何从框架的SendAsync方法中调用它呢?

public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        Email email = new Email();
        return Task.FromResult<void>(email.Send(message));
    }
}

在上面的代码中,Task.FromResult() 给我一个错误,说 void 不能用作参数类型。但是 email.Send() 返回 void!
如何走出这个困境?

3
你对于在C#中使用usingasync关键字有什么不喜欢的地方(例如在https://dev59.com/L2Qn5IYBdhLWcg3wzZtU中展示的)? - Alexei Levenkov
@AlexeiLevenkov:我发现有些例子说这不是有效的代码,而且我看到SmtpClient.SendAsync()需要一个第二个参数,似乎与回调有关。这就是我想避免的。 - Jonathan Wood
3
OT: SmtpClient的文档建议使用MailKitMimeKit。 简化翻译:使用SmtpClient时,文档建议使用MailKit和MimeKit。 - Paulo Morgado
1个回答

9
如果没有结果,那就不要试图返回一个结果。只需返回一个简单、已完成的Task:
public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        new Email().Send(message);
        return Task.CompletedTask;
    }
}

如果您仍然停留在4.6之前的版本,则可以使用Task.FromResult<bool>(true)代替。
话虽如此,我对您关于"async...awaitusing不兼容"的评论感到困惑。根据我的经验,这是有效的。如果您的方法实际上是异步的,那将会更好。我认为您最好专注于如何做到这一点,而不是伪造async方法的最佳/正确语法。
补充:
我仍然不清楚您对使用using的担忧。但是,根据您的评论,似乎您想使用SmtpClient.SendAsync(),但不确定如何在async/await的上下文中应用它。
不幸的是,在async/await之前,.NET中有许多异步方法,并且这些方法使用了与新可等待方法约定相同的命名。 (要明确:不幸的是命名方式,而不是异步方法存在:))。但是,在所有情况下,都可以将旧API适应新API。
在某些情况下,只需使用Task.FromAsync()方法即可。这适用于支持旧的Begin.../End...模型的任何内容。但是,SmtpClient.SendAsync()模型是基于事件回调的方法,需要稍微不同的方法。
注意:在编写下面示例之后,我注意到SmtpClient类具有基于Task的异步操作方法SendMailAsync()。因此,实际上没有必要适应旧的SendAsync()方法。但是,这是一个有用的示例,可以展示如何在未提供基于Task的替代方案时进行适应。简而言之,您可以使用TaskCompletionSourceSmtpClient对象上的SendCompleted事件。以下是大致的外观:
public class EmailService : IIdentityMessageService
{
    public async Task SendAsync(IdentityMessage message)
    {
        // I'm not familiar with "IdentityMessage". Let's assume for the sake
        // of this example that you can somehow adapt it to the "MailMessage"
        // type required by the "SmtpClient" object. That's a whole other question.
        // Here, "IdentityToMailMessage" is some hypothetical method you write
        // to handle that. I have no idea what goes inside that. :)
        using (MailMessage mailMessage = IdentityToMailMessage(message))
        using (SmtpClient smtpClient = new SmtpClient())
        {
            TaskCompletionSource<bool> taskSource = new TaskCompletionSource<bool>();

            // Configure "smtpClient" as needed, such as provided host information.
            // Not shown here!

            smtpClient.SendCompleted += (sender, e) => taskSource.SetResult(true);
            smtpClient.SendAsync(mailMessage, null);

            await taskSource.Task;
        }
    }
}

上述代码将启动异步操作,并使用 SendCompleted 事件处理程序(即文档所指的“回调”)来设置 TaskCompletionSource 对象的结果(结果值实际上从未被使用,但没有纯粹的 Task 版本的 TaskCompletionSource…必须有一些值)。
它使用 await 而不是直接返回 taskSource.Task 对象,因为这样可以在电子邮件操作实际完成时正确处理 SmtpClient 对象的释放。

我已经拥有最新的程序代码。但是我发现许多示例在异步方法中避免使用 using 块。我正在深入研究这个问题,但是 SmtpClient.SendAsync() 需要一个第二个参数,该参数似乎只有在某种回调上下文中才有意义。 - Jonathan Wood
如果您不需要回调数据,可以将null传递给回调函数。请参见上面的编辑,了解如何将SmtpClient.SendAsync()方法适应于async/await模式。 - Peter Duniho
我之前没有注意到,但实际上,在 SmtpClient 中有一个与 Task 兼容的发送方法:SendMailAsync()。我将保留以上内容,作为将基于事件的异步 API 通常适应为基于 Task 的示例,但实际上在这种特定情况下,最好只使用 SendMailAsync() 方法。 - Peter Duniho
感谢您的额外评论。在您更新之前,我也刚刚发现了SendMailAsync()。它似乎表现得符合预期,而且我可能会使用它。但这确实有点令人困惑。 - Jonathan Wood
当然,在TPL出现之前,SendAsync()方法早已存在,更不用说async/await了。所以我理解为什么会有两个不同的方法。如果在C#中,一个方法的重载只能通过返回类型来区分,他们甚至可能会保留相同的名称。但是他们不能这样做。我不确定我是否会选择SendMailAsync()这个名称,因为它与SendAsync()并没有太大的区别,也没有充分地区分这两者。但他们没有问我的意见。 :) - Peter Duniho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接