将 TPL 与异步等待一起使用时无法添加日志记录

Not able to add logging when using TPL with async await

我一直在寻找这个问题的答案但没有成功,所以在这里试试。 我正在尝试在任务中使用我的代码进行日志记录。 我试图尽可能地简化我的示例。

    public void ExecuteRequestOnNewTask(string clientRequestContent)
    {
        WriteToTextFile("just entered ExecuteRequestOnNewTask");
        try
        {
            string response = string.Empty;
            Task.Factory.StartNew(
                async () =>
                {
                    WriteToTextFile("About to call ExecuteRequest");
                    response = await ExecuteRequest(clientRequestContent);
                    WriteToTextFile("Response received:" + response);
                });
        }
        catch (Exception ex)
        {
            WriteToTextFile("Exception from task:" + ex);
        }
    }

    public async Task<string> ExecuteRequest(string clientRequestContent)
    {
        // long running stuff here
    }

    public void WriteToTextFile(string text)
    {
        string textWithCurrentDateTime = "\r\n" + DateTime.UtcNow.AddHours(1) + ": " + text;
        string path = @"C:\Log.txt";
        if (!File.Exists(path))
        {
            File.Create(path);
            TextWriter tw = new StreamWriter(path);
            tw.WriteLine(textWithCurrentDateTime);
            tw.Close();
        }
        else if (File.Exists(path))
        {
            TextWriter tw = new StreamWriter(path, true);
            tw.WriteLine(textWithCurrentDateTime);
            tw.Close();
        }
    }

我在日志文件中得到的唯一输出是:

"just entered ExecuteRequestOnNewTask" "About to call ExecuteRequest"

我没有记录响应,也没有在长 运行 方法中记录任何内容。

代码有几个问题:

  • Task.Factory.StartNewlow-level, dangerous method。它有一个不明显的默认任务调度程序,并且不理解 async 委托。如果您想 运行 在线程池线程上编写代码,请改用 Task.Run
  • StartNew返回的任务被忽略。因此,catch 块永远不会做任何事情。如果你想在一劳永逸的场景中捕获并记录异常,你需要在委托中完成。换句话说,你不能从已经被解雇和遗忘的东西中捕获异常,因为它已经被遗忘!

此外,WriteToTextFile不是线程安全的,这在这里肯定是个问题。但是 t运行cated 日志最可能的原因是即发即弃。如果您的应用程序退出,即发即弃任务将终止 - 无一例外,无通知。这是设计使然 - 毕竟,它们是 即发即弃

如果您想记录即发即弃任务的异常,您可以使用以下一个不错的扩展方法:

public static Task OnExceptionLogError(this Task task, string message)
{
    task.ContinueWith(t =>
    {
        var exception = t.Exception;
        var innerExceptions = exception.Flatten().InnerExceptions;
        var lines = innerExceptions.Select(ex => $"{ex.GetType().FullName}: {ex.Message}");
        string literal = innerExceptions.Count == 1 ? "an exception" : $"{innerExceptions.Count} exceptions";
        WriteToTextFile($"{message}, {literal} occured on task #{task.Id}:\r\n  " + String.Join("\r\n  ", lines));
    }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
    return task;
}

用法示例:

Task.Factory.StartNew(
    async () =>
    {
        WriteToTextFile("About to call ExecuteRequest");
        response = await ExecuteRequest(clientRequestContent);
        WriteToTextFile("Response received:" + response);
    }).OnExceptionLogError("ClientRequestContent"); // <--- give a title for the error

它通过调用 Task.WhenAll 记录所有可能聚合的嵌套错误。


这是另一种记录即发即弃任务异常的方法。它的优点是它是一个位于一个地方的全局处理程序,缺点是它无法记录除异常本身之外的有关错误的更多信息。

static void Main(string[] args)
{
    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
    // ...
}

private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
    // Log the e.Exception, which is an AggregateException
    WriteToTextFile("Exception from task:" + e.Exception);
}

更新: 注意事项:

1) TaskScheduler.UnobservedTaskException 事件在任务被垃圾收集时引发,这可能会在很久以后发生或根本不会发生!

2) 调试版本不会引发此事件,only on Release builds!