2017-10-02 4 views
2

J'ai un service Windows (en utilisant Topshelf) qui repose sur la bibliothèque tierce EasyRedisMQ. Malheureusement, je viens de découvrir le code suivant dans l'une des méthodes de la bibliothèque:Comment intercepter une exception déclenchée par une tâche qui n'est pas attendue dans une bibliothèque tierce?

public async Task InitializeAsync() 
    { 
     if (SubscriberInfo == null) throw new NullReferenceException("SubscriberInfo is required."); 
     if (string.IsNullOrWhiteSpace(SubscriberInfo.SubscriberId)) throw new NullReferenceException("SubscriberId is required"); 
     if (string.IsNullOrWhiteSpace(SubscriberInfo.ExchangeName)) throw new NullReferenceException("ExchangeName is required"); 
     if (string.IsNullOrWhiteSpace(SubscriberInfo.QueueName)) throw new NullReferenceException("QueueName is required"); 
     if (OnMessageAsync == null) throw new NullReferenceException("OnMessageAsync is required"); 

     await _cacheClient.SubscribeAsync<string>(SubscriberInfo.ExchangeName, DoWorkAsync); 

     DoWorkAsync("").FireAndForget(); 
    } 

Ici DoWorkAsync retourne une tâche, mais indique tire et oublie, cela est malheureusement pas attendu. En fait, FireAndForget est une méthode avec un corps vide (le seul but est de rendre explicitement que la tâche n'est pas attendue). Voir le code source complet here.

Le problème est qu'une exception est parfois jeté DoWorkAsync, ce qui provoque le service accident:

{ 
    "Depth": 0, 
    "ClassName": "StackExchange.Redis.RedisConnectionException", 
    "Message": "SocketFailure on RPOP", 
    "Source": "mscorlib", 
    "StackTraceString": " at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at StackExchange.Redis.Extensions.Core.StackExchangeRedisCacheClient.<ListGetFromRightAsync>d__67`1.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at EasyRedisMQ.Models.Subscriber`1.<GetNextMessageAsync>d__12.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at EasyRedisMQ.Models.Subscriber`1.<DoWorkAsync>d__13.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at StackExchange.Redis.Extensions.Core.StackExchangeRedisCacheClient.<>c__DisplayClass59_0`1.<<SubscribeAsync>b__0>d.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c.<ThrowAsync>b__6_1(Object state)\r\n at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)\r\n at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)\r\n at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)\r\n at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()\r\n at System.Threading.ThreadPoolWorkQueue.Dispatch()\r\n at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()", 
... 
} 

J'aimerais beaucoup attraper l'exception (comme il est en fait inoffensif et simplement recommencer l'opération) . Cependant, après avoir lu this StackOverflow question et expérimenté avec AppDomain.CurrentDomain.FirstChanceException, je n'ai pas de grands espoirs. Le problème avec FirstChanceException est qu'il se produit en dehors de la méthode de Tophelf

HostFactory.Run(hostConfigurator => { ... } 

, où je la référence au service (où la logique métier réside):

hostConfigurator.Service<IConsumer>(serviceConfigurator => 
{ 
    serviceConfigurator.ConstructUsing(() => IocContainer.IocContainer.Instance.Resolve<IConsumer>()); 
    serviceConfigurator.WhenStarted(consumer => { /* Here I have control */ }); 
    ... 
} 

Quelqu'un at-il des idées sur la façon pour faire face à cette situation?

+2

En regardant ce code, que l'on appelle avec 'FireAndForget' ne devrait jamais jeter. Donc, cela pourrait être un bug potentiel. Peut-être que vous devriez déposer un problème. – Iqon

+0

Malheureusement, la bibliothèque client n'a pas été mise à jour depuis 2 ans, donc je ne suis pas sûr que le dépôt d'un bug vous aidera. Mais je suis entièrement d'accord que cela devrait idéalement être géré dans la bibliothèque elle-même. – SabrinaMH

+2

Je n'ai aucune expérience avec 'EasyRedisMQ', mais dans la source que vous avez donnée, il y a un appel à' PushMessageToSubscriberAsync', donc il pourrait y avoir un moyen de s'abonner à ce message. Il devrait être distribué, même s'il y avait une exception le traitant. – Iqon

Répondre

1

Abonnez-vous à event de TaskScheduler:

TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; 
//... 

private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) 
{ 
    e.SetObserved(); 
} 
+0

Merci pour la suggestion. J'ai effectivement regardé dans TaskScheduler.UnobservedTaskException aussi, mais le problème est que j'ai besoin d'ajouter du code pour réessayer l'opération, et dans TaskScheduler_UnobservedTaskException je ne peux invoquer aucune méthode sur l'objet consommateur (spécifié dans serviceConfiguration.WhenStarted (consumer = > {...}); – SabrinaMH