I have a Windows Service (using Topshelf) that relies on the third party library EasyRedisMQ . Unfortunately, I just discovered the following code in one of the library methods:
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();
}
Here DoWorkAsync returns a task, but as FireAndForget indicates, this is unfortunately not awaited. Actually, FireAndForget is a method with an empty body (the only purpose is to make it explicitly that the task is not awaited). See the full source code here .
The problem is that an exception occasionally is thrown in DoWorkAsync, which causes the service to crash:
{
"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()",
...
}
I would very much like to catch the exception (as it's actually harmless and simply retry the operation). However, after reading this StackOverflow question and experimenting with AppDomain.CurrentDomain.FirstChanceException, I do not have high hopes. The problem with FirstChanceException is that it occurs outside Tophelf's
HostFactory.Run(hostConfigurator => { ... }
method, in which I have the reference to the service (where the business logic resides):
hostConfigurator.Service<IConsumer>(serviceConfigurator =>
{
serviceConfigurator.ConstructUsing(() => IocContainer.IocContainer.Instance.Resolve<IConsumer>());
serviceConfigurator.WhenStarted(consumer => { /* Here I have control */ });
...
}
Has anyone any ideas on how to deal with this situation?
Subscribe to event of TaskScheduler:
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
//...
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
e.SetObserved();
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.