Envoi de courrier async à partir du hub SignalR

Je dois envoyer un courrier électronique à la suite d’une invocation de hub SignalR. Je ne souhaite pas que l’envoi s’exécute de manière synchrone, car je ne souhaite pas bloquer les connexions WebSocket, mais j’aimerais que l’appelant soit informé, si possible, des éventuelles erreurs. Je pensais pouvoir utiliser quelque chose comme ceci dans le hub (moins la gestion des erreurs et toutes les autres choses que je veux que ce soit fait):

public class MyHub : Hub { public async Task DoSomething() { var client = new SmtpClient(); var message = new MailMessage(/* setup message here */); await client.SendMailAsync(message); } } 

Mais bientôt découvert que cela ne fonctionnerait pas; l’appel client.SendMailAsync lève ceci:

System.InvalidOperationException: une opération asynchrone ne peut pas être démarrée pour l’instant. Les opérations asynchrones ne peuvent être démarrées que dans un gestionnaire ou un module asynchrone ou lors de certains événements du cycle de vie de la page.

Des investigations et des lectures plus approfondies m’ont montré que SmtpClient.SendMailAsync est un wrapper TAP autour des méthodes EAP et que SignalR ne le permet pas.

Ma question est la suivante: existe-t-il un moyen simple d’envoyer des e-mails de manière asynchrone directement à partir d’une méthode hub?

Ou est-ce que ma seule option est de placer le code d’envoi du courrier électronique ailleurs? (par exemple, le concentrateur doit-il mettre en queue un message de bus de service, puis un service autonome pourrait gérer ces messages et envoyer les courriels [bien que j’aurais également plus de travail pour implémenter la notification des résultats aux clients du concentrateur]; ou le hub envoie une requête HTTP à un service Web qui envoie l’email).

Des questions similaires ici et ici .

L’équipe de SignalR est consciente du problème , mais ne l’a pas encore résolu. À ce stade, il semble que cela ira dans SignalR v3.

En attendant, un rapide piratage serait le suivant:

 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); } } 

D’après ce que je comprends, SmtpClient.SendAsync (qui est SendMailAsync par SendMailAsync tant que TAP) de style EAP d’origine appelle SynchronizationContext.Current.OperationStarted / OperationCompleted . C’est prétendument ce qui rend malheureux l’hôte de SignalR.

Pour contourner le problème, essayez-le de cette façon (non testé). Faites-nous savoir si cela fonctionne pour vous.

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