簡體   English   中英

C# 異步技術用於寫入日志文件但首先等待前一個日志 I/O 完成

[英]C# async technique for writing to log file but first wait for previous log I/O to finish

我有時需要寫入一個日志文件,有時是一連串快速的日志請求,但不想等待 I/O。 但是,我想要等待的是在寫入 NEXT 日志條目之前完成 I/O(如 stream 關閉)。 因此,如果第一個日志 I/O 請求很忙,后續的 I/O 請求將禮貌地排隊等待輪到它們,而不是互相踩踏。

我已經拼湊了一個想法,有什么理由為什么這行不通嗎? 使用框架 4.7.2 和 4.8,asp.net MVC web 應用程序。

我在其他地方定義了一個 static 任務 t 所以它對應用程序是全局的。

public static void ErrorLog(string file, string error)
{
    if (t != null)
        t.Wait();
    //using file system async - doesn't use thread pool
    var f = new FileStream(Path.Combine(HttpRuntime.AppDomainAppPath, "logs", file), FileMode.Append, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true);
    var sWriter = new StreamWriter(f);
    t = sWriter.WriteLineAsync($"### {error}").ContinueWith(c => sWriter.Close());
}

這似乎有效,只需進行簡單的壓力測試,例如:

ErrorLog("test.txt", string.Join(" ", Enumerable.Range(i++, 1000)));

重復了無數次。 變量 i 只是為了讓我可以在日志中按順序查看每個寫入。

美妙之處在於我不需要重寫所有異步請求並將 ErrorLog 轉換為真正的異步 function。 哪個是理想的,但是今天要修改的代碼太多了。

我擔心的是最后一次寫入,盡管它似乎在 web 請求完成時拆除 AppDomain 之前完成,但我認為這不是任何保證......我想知道我是否需要做一個 t.Wait( ) 在每個可能寫入日志的傳入 web 請求的末尾...只是為了確保在結束請求之前最后一個日志條目是完整的...

您的問題是您沒有等待寫入的Task結果,這意味着 AppDomain 可以在中間被拆除。

理想情況下,如果您只是要等待寫入,您可以這樣做:

public static async Task ErrorLog(string file, string error)
{
    //using file system async - doesn't use thread pool
    using (var f = new FileStream(Path.Combine(HttpRuntime.AppDomainAppPath, "logs", file), FileMode.Append, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
    using (var sWriter = new StreamWriter(f))
    {
        await sWriter.WriteLineAsync($"### {error}"):
    }
}

但是,這不允許您在不等待的情況下移交日志寫入。 相反,您需要實現一個BackgroundService和一個要寫入的日志隊列。

一個非常粗略的實現將是這樣的:

public class LoggingService : BackgroundService
{
    private Channel<(string file, string error)> _channel = new Channel.CreateUnbounded<(string, string)>();

    protected override async Task ExecuteAsync(CancellationToken token)
    {
        while(true)
        {
            try
            {
                var (file, error) = await _channel.Reader.ReadAsync(token);
                await WriteLog(file, error, token);
            }
            catch (OperationCanceledException)
            {
                break;
            }
        }
    }

    private async Task WriteLog(string file, string error, CancellationToken token)
    {
        using (var f = new FileStream(Path.Combine(HttpRuntime.AppDomainAppPath, "logs", file), FileMode.Append, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
        using (var sWriter = new StreamWriter(f))
        {
            await sWriter.WriteLineAsync($"### {error}".AsMemory(), token):
        }
    }

    public async Task QueueErrorLog(string file, string error)
    {
        await _channel.Writer.WriteAsync((file, error));
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM