从SignalR中心发送异步邮件

我需要通过SignalR集线器调用发送电子邮件。 我不希望send同步执行,因为我不想绑定WebSocket连接,但是如果可能的话,我希望通知调用者是否有任何错误。 我以为我可以在集线器中使用这样的东西(减去error handling和我希望它做的所有其他事情):

public class MyHub : Hub { public async Task DoSomething() { var client = new SmtpClient(); var message = new MailMessage(/* setup message here */); await client.SendMailAsync(message); } } 

但很快发现它不起作用; client.SendMailAsync调用抛出这个:

System.InvalidOperationException:此时无法启动异步操作。 异步操作只能在异步处理程序或模块中启动,或者在页面生命周期中的某些事件中启动。

进一步的调查和阅读表明,SmtpClient.SendMailAsync是围绕EAP方法的TAP包装器,而SignalR不允许这样做。

我的问题是,有没有简单的方法直接从集线器方法发送电子邮件?

或者我唯一的选择是将电子邮件发送到其他地方? (例如,让集线器队列成为服务总线消息,然后一个独立的服务可以处理这些消息并发送电子邮件[虽然我也有更多的工作来实现将结果通知回集线器的客户端];或者让集线器向发送电子邮件的Web服务发出HTTP请求。

类似的问题在这里和这里 。

SignalR团队意识到了这个问题 ,但尚未解决。 在这一点上,它似乎将进入SignalR v3。

与此同时,一个快速的黑客将是这样的:

 public async Task DoSomething() { using (new IgnoreSynchronizationContext()) { var client = new SmtpClient(); var message = new MailMessage(/* setup message here */); await client.SendMailAsync(message); } } public sealed class IgnoreSynchronizationContext : IDisposable { private readonly SynchronizationContext _original; public IgnoreSynchronizationContext() { _original = SynchronizationContext.Current; SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); } public void Dispose() { SynchronizationContext.SetSynchronizationContext(_original); } } 

我的理解是,原始的EAP风格的SmtpClient.SendAsync (由SendMailAsync作为TAP包装)调用SynchronizationContext.Current.OperationStarted / OperationCompleted 。 据称,这是使SignalR主持人不满的原因。

作为一种解决方法,请尝试这种方式(未经测试)。 如果它适合您,请告诉我们。

 public class MyHub : Hub { public async Task DoSomething() { var client = new SmtpClient(); var message = new MailMessage(/* setup message here */); await TaskExt.WithNoContext(() => client.SendMailAsync(message)); } } public static class TaskExt { static Task WithNoContext(Func func) { Task task; var sc = SynchronizationContext.Current; try { SynchronizationContext.SetSynchronizationContext(null); task = func(); } finally { SynchronizationContext.SetSynchronizationContext(sc); } return task; } }