[英]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.