从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团队意识到了这个问题,但还没有修复它。现在看起来它会进入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;
}
}