【问题标题】:How to catch exception thrown by a task that's not awaited in a third party library?如何捕获第三方库中未等待的任务引发的异常?
【发布时间】:2018-03-13 16:27:32
【问题描述】:

我有一个依赖于第三方库 EasyRedisMQ 的 Windows 服务(使用 Topshelf)。不幸的是,我刚刚在其中一个库方法中发现了以下代码:

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

这里 DoWorkAsync 返回一个任务,但正如 FireAndForget 所指出的,不幸的是,这没有等待。实际上,FireAndForget 是一个具有空主体的方法(唯一的目的是明确地表明该任务不处于等待状态)。 查看完整源代码here

问题是DoWorkAsync偶尔会抛出异常,导致服务崩溃:

{
  "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()",
...
}

我非常想捕捉异常(因为它实际上是无害的,只需重试操作)。但是,在阅读了this StackOverflow question 并尝试了 AppDomain.CurrentDomain.FirstChanceException 之后,我并没有寄予厚望。 FirstChanceException 的问题在于它发生在 Tophelf 的

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

方法,我在其中引用了服务(业务逻辑所在的位置):

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

有人对如何处理这种情况有任何想法吗?

【问题讨论】:

  • 看看这段代码,用FireAndForget 调用的东西永远不会抛出。所以这可能是一个潜在的错误。也许你应该提出问题。
  • 不幸的是,客户端库已经 2 年没有更新了,所以我不相信提交错误会有所帮助。但我完全同意这应该在图书馆本身中处理。
  • 我对@9​​87654329@ 没有任何经验,但是在您提供的源代码中,有一个对PushMessageToSubscriberAsync 的调用,因此可能有一种方法可以订阅此消息。它应该被调度,即使有一个异常处理它。
  • 为什么不分叉回购并根据自己的喜好进行修复?也许作者还是愿意接受 PR 的。
  • @Iqon 这是一个很好的收获!然而问题是,由于 GetNextMessageAsync() 行引发了异常,因此它在任何 catch 子句之外 - 因此它使我的服务崩溃。

标签: c# exception async-await task topshelf


【解决方案1】:

订阅TaskScheduler的event

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

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

【讨论】:

  • 感谢您的建议。我实际上也查看了 TaskScheduler.UnobservedTaskException,但问题是我需要添加一些代码来重试操作,并且在 TaskScheduler_UnobservedTaskException 中我无法调用消费者对象上的任何方法(在 serviceConfiguration.WhenStarted(consumer = > { ... });
猜你喜欢
  • 2017-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多