从SignalR hub发送异步邮件

本文关键字:异步 SignalR hub | 更新日期: 2023-09-27 18:10:04

我需要发送一封电子邮件作为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);
    }
}

但很快发现这行不通;客户端。SendMailAsync调用抛出这个:

系统。InvalidOperationException:此时不能启动异步操作。异步操作只能在异步处理程序或模块中启动,或者在Page生命周期中的某些事件中启动。

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

我的问题是,是否有任何简单的方法可以直接从hub方法异步发送电子邮件?

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

从SignalR hub发送异步邮件

这里和这里都有类似的问题。

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<Task> func)
    {
        Task task;
        var sc = SynchronizationContext.Current;
        try
        {
            SynchronizationContext.SetSynchronizationContext(null);
            task = func();
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(sc);
        }
        return task;
    }
}