以简化形式,我的代码如下所示:
async Task Foo() {
while(true) {
...
lock (syncObj) {
...
File.AppendAllText(FileName, data);
}
}
}
void Main() {
...
tasks.Add(Task.Factory.StartNew(Foo, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap());
...
}
我很少得到IOException
:该进程无法访问文件“路径”,因为它正在被另一个进程使用。如果对文件的访问是通过锁结构同步的,这怎么可能呢?代码中的其他任何地方都不会访问该文件,并且没有其他进程正在使用该文件。也许这是WinAPI调用背后的某种特性AppendAllText
,很少能在文件释放之前执行完成?
.NET 6.0、Windows
lock
禁止在异步函数内使用。据我了解,这类似于日志记录。在这种情况下,您需要最大限度地减少 I/O 操作次数,从而提高写入性能并减少磁盘负载。每秒写入 200 次不需要打开/关闭文件,速度很慢。正如@rotabor 所示,这里可以使用队列。是的,你是对的,在某些情况下,操作系统可能没有时间在关闭后释放文件,因为根据文件系统的性能,在极少数情况下,文件可能会异步关闭,理论上你可以捕获同样的例外。
我建议将写操作组合成组。也就是说,我们以一定的频率在一次操作中读取缓冲区并写入文件,然后在文件关闭的情况下等待下一部分。
写入管道是线程安全的操作。
该解决方案的本质是文件不会为每个条目关闭并重新打开。通道将等待数据到达,一旦到达,文件就会打开并进行连续记录,直到要写入的行队列完全清除。然后文件将关闭并等待下一部分数据。它将保证数据不会丢失并且将被记录,如果不是第一次,则在下一次尝试时
try-catch
。当应用程序退出时
logger.Complete()
,只需调用(永久)关闭通道,该方法Foo
就会正常退出。本质上,它是生产者/消费者设计模式的实现。您只需运行该方法一次,Foo
以便它处理进入通道的所有内容直至结束。或者如果有多个文件要写入,那么有多少个文件就有多少个方法。每个文件都需要自己单独的通道。了解通道:System.Threading.Channels 简介
我将通过 Task.ContinueWith 来完成此操作,并且不需要syncObj。对我来说效果很好。
变量初始化有条件地与声明结合起来。
为什么要创建一个异步函数来同步对文件的访问?胡说些什么?
将与文件的交互完全移到其他地方。使用
async
/创建一个普通的异步方法await
。通过 StreamWriter 访问文件,一切都会好起来的。这表明了生产者/消费者模式。多个线程写入线程安全集合(最好采用现代集合
Channel
),一个线程从通道中抓取消息并写入文件。SO 上有使用示例
Channel
。