以线程安全的方式写入文件

Stringbuilder写入异步文件。 此代码控制文件,将流写入其中并释放它。 它处理来自异步操作的请求,这些请求可能随时出现。

FilePath是按类实例设置的(因此锁定Object是每个实例),但是由于这些类可能共享FilePaths ,因此可能存在冲突。 这种冲突以及来自类实例之外的所有其他类型将被处理重试。

这段代码适合它的用途吗? 有没有更好的方法来处理这个问题,这意味着对捕获和重试机制的依赖程度更低(或没有)?

另外,我如何避免捕获因其他原因而发生的exception。

 public string Filepath { get; set; } private Object locker = new Object(); public async Task WriteToFile(StringBuilder text) { int timeOut = 100; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); while (true) { try { //Wait for resource to be free lock (locker) { using (FileStream file = new FileStream(Filepath, FileMode.Append, FileAccess.Write, FileShare.Read)) using (StreamWriter writer = new StreamWriter(file, Encoding.Unicode)) { writer.Write(text.ToString()); } } break; } catch { //File not available, conflict with other class instances or application } if (stopwatch.ElapsedMilliseconds > timeOut) { //Give up. break; } //Wait and Retry await Task.Delay(5); } stopwatch.Stop(); } 

你如何处理这个问题很大程度上取决于你写作的频率。 如果你不经常写一个相对少量的文本,那么只需使用一个静态锁并完成它。 在任何情况下,这可能是您最好的选择,因为磁盘驱动器一次只能满足一个请求。 假设您的所有输出文件都在同一个驱动器上(可能不是一个公平的假设,但请耐心等待),在应用程序级别锁定与在操作系统级别完成的锁定之间没有太大区别。

因此,如果您将locker声明为:

 static object locker = new object(); 

您可以放心,您的程序中没有与其他线程冲突。

如果你想要这个东西是防弹的(或者至少是合理的话),你就无法摆脱捕捉exception。 坏事可能发生。 您必须以某种方式处理exception。 你在面对错误时做的事情完全不同。 如果文件被锁定,您可能想要重试几次。 如果您收到错误的路径或文件名错误或磁盘已满或任何其他错误,您可能想要终止程序。 再一次,这取决于你。 但是你无法避免exception处理,除非你对程序崩溃时的错误感到满意。

顺便说一句,您可以替换所有这些代码:

  using (FileStream file = new FileStream(Filepath, FileMode.Append, FileAccess.Write, FileShare.Read)) using (StreamWriter writer = new StreamWriter(file, Encoding.Unicode)) { writer.Write(text.ToString()); } 

只需一个电话:

 File.AppendAllText(Filepath, text.ToString()); 

假设您使用的是.NET 4.0或更高版本。 请参见File.AppendAllText 。

另一种可以处理此问题的方法是让线程将其消息写入队列,并具有为该队列提供服务的专用线程。 您有一个消息和相关文件路径的BlockingCollection 。 例如:

 class LogMessage { public string Filepath { get; set; } public string Text { get; set; } } BlockingCollection _logMessages = new BlockingCollection(); 

您的线程将数据写入该队列:

 _logMessages.Add(new LogMessage("foo.log", "this is a test")); 

您启动一个长时间运行的后台任务,除了为该队列提供服务外什么都不做

 foreach (var msg in _logMessages.GetConsumingEnumerable()) { // of course you'll want your exception handling in here File.AppendAllText(msg.Filepath, msg.Text); } 

这里潜在的风险是线程创建的消息太快,导致队列无限制地增长,因为消费者无法跟上。 这是否是您应用程序中的真正风险,只有您可以说。 如果您认为这可能存在风险,则可以在队列中放置最大大小(条目数),以便在队列大小超过该值时,生成器将等待,直到队列中有空间才能添加。

你也可以使用ReaderWriterLock ,在处理读写操作时,它被认为是控制线程安全的更“合适”的方法……

要调试我的Web应用程序(当远程调试失败时)我使用以下(’debug.txt’结束在服务器上的\ bin文件夹中):

 public static class LoggingExtensions { static ReaderWriterLock locker = new ReaderWriterLock(); public static void WriteDebug(string text) { try { locker.AcquireWriterLock(int.MaxValue); System.IO.File.AppendAllLines(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Replace("file:\\", ""), "debug.txt"), new[] { text }); } finally { locker.ReleaseWriterLock(); } } } 

希望这能为您节省一些时间。