从SignalR集线器发送异步邮件

时间:2014-06-24 01:37:49

标签: c# email signalr async-await smtpclient

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

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请求。

2 个答案:

答案 0 :(得分:7)

类似问题herehere

SignalR团队是aware of the issue,但还没有解决它。此时看起来它会进入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);
  }
}

答案 1 :(得分:4)

我的理解是,原始的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<Task> func)
    {
        Task task;
        var sc = SynchronizationContext.Current;
        try
        {
            SynchronizationContext.SetSynchronizationContext(null);
            task = func();
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(sc);
        }
        return task;
    }
}
相关问题