我需要通过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请求。
答案 0 :(得分:7)
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;
}
}