Отправка асинхронной почты из концентратора SignalR
Мне нужно отправить электронное сообщение в результате вызова хаба SignalR. Я не хочу, чтобы передача выполнялась синхронно, так как я не хочу связывать соединения 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 является оберткой TAP вокруг методов EAP и что SignalR не позволяет этого.
Мой вопрос в том, есть ли простой способ асинхронной отправки электронных писем непосредственно из метода хаба?
Или мой единственный вариант поместить код отправки электронной почты в другое место? (например, для очереди хабов сообщение служебной шины, то автономная служба может обрабатывать эти сообщения и отправлять электронные письма [хотя у меня также было бы больше работы таким образом, чтобы реализовать уведомление о результатах назад для клиентов-концентраторов); иметь концентратор, сделать HTTP-запрос к веб-сервису, который отправляет электронную почту).
Ответы
Ответ 1
Аналогичные вопросы здесь и здесь.
Команда 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);
}
}
Ответ 2
Насколько я понимаю, оригинальный 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;
}
}