【问题标题】:Logging exceptions in fire&forget tasks在 fire&forget 任务中记录异常
【发布时间】:2019-06-12 10:41:26
【问题描述】:

我尝试在后台启动一些操作,我对其结果不感兴趣,但如果出现错误,我想记录它,并且 - 当然 - 阻止应用程序(这里:Windows 服务)崩溃。

    public static void CreateAndStartTaskWithErrorLogging(Action _action, string _componentName, string _originalStacktrace = null)
    {
        DateTime started = HighPrecisionClock.Now;
        Task task = new Task(_action);
        task.ContinueWith(_continuation => _continuation.LogExceptions(_componentName, started, _originalStacktrace));
        task.ConfigureAwait(false);
        task.Start();
    }
    internal static void LogExceptions(this Task _t, string _componentName, DateTime _started, string _originalStacktrace = null)
    {
        try
        {
            _t.Wait(1000);
        }
        catch (Exception ex)
        {
            Logger.LogError(_componentName, $"An exception occurred in a fire-and-forget task which was started at {_started}.\r\n" +
                                            $"The original stack trace is:\r\n{_originalStacktrace}");
            Logger.LogException(_componentName, ex);
        }
        try
        {
            _t.Dispose();
        }
        catch (Exception dex)
        {
            Logger.LogException(_componentName, dex);
        }
    }

没有ConfigureAwait(false) 和没有_t.Dispose()catch 工作并记录异常。但是应用程序在几秒钟后崩溃(即在 Finalizer 线程上?)。 Microsoft 事件查看器中的条目显示该异常。

使用ConfigureAwait_t.Dispose(),我在日志中看不到异常,应用程序只是崩溃了。

上面的想法有什么问题?

编辑:

同时我在没有ConfigureAwait 的情况下进行了测试,但使用了_t.Dispose。我可以捕获大约 10 个这样的异常,但没有一个会导致应用程序崩溃。这似乎解决了问题,但我不明白其中的原因,所以情况仍然很糟糕。

ConfigureAwait(false) 对任务中的异常(或在该任务中启动的任务,例如由 Parallel.ForEach 进一步向下)执行什么操作?

为什么Dispose - 在延续时调用,而不是根据评论正确的任务 - 防止崩溃(终结器不调用 Dispose,但 Dispose 可能设置一些影响其行为的标志)?

编辑 2:

这也不是一直有效,只有大部分时间。下面建议的解决方案 1 有时也会失败。

在崩溃上下文中,使用Utilities.TaskExtensions.CreateAndStartTaskWithErrorLogging(() => DataStore.StoreSyncedData(data), Name); 调用该函数,其中DataStore 设置为一个组合,该组合依次调用其成员上的Parallel.ForEach(m_InnerDataStores, _store => { _store.StoreSyncedData(_syncedData); });。其中一个使用 Accord 库编写视频,有时会在<Module>.avcodec_encode_video2(libffmpeg.AVCodecContext*, libffmpeg.AVPacket*, libffmpeg.AVFrame*, Int32*) 处导致AccessViolation,即异常可能来自非托管代码。

当然,我可以尝试在下面的某个地方捕捉它——但这不是这种方法的目标。我希望它能够在后台安全地运行任何代码而不会导致应用程序崩溃。

【问题讨论】:

  • 不要猜测错误在哪里,发布完整的堆栈跟踪。
  • 在您的情况下,_t 不是task,而是延续本身的任务,因此该任务自行处理。

标签: c# exception task


【解决方案1】:

这是我对记录错误的建议:

public static void OnExceptionLogError(this Task task, string message)
{
    task.ContinueWith(t =>
    {
        // Log t.Exception
    }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
}

使用示例:

var task = Task.Run(action);
task.OnExceptionLogError("Oops!");
try
{
    await task;
}
catch
{
    // No need to log exception here
}

【讨论】:

  • 感谢您的建议。在最后 6 个错误中,它可以捕获其中的 5 个。有一个错误,它甚至没有记录错误,但应用程序崩溃了。
猜你喜欢
  • 2020-03-21
  • 2014-10-20
  • 2023-01-14
  • 2019-07-07
  • 1970-01-01
  • 2014-05-16
  • 1970-01-01
  • 2011-02-07
  • 2013-01-13
相关资源
最近更新 更多