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

Nat*_*per 26 c# filesystems multithreading thread-safety

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

FilePath每类实例(所以锁组件,Object每个实例),但有潜在的冲突,因为这些类可以共享FilePaths.这种冲突以及来自类实例之外的所有其他类型将被处理重试.

这段代码是否适合其目的?有没有更好的方法来处理这个意味着更少(或没有)依赖捕获和重试机制?

另外,我如何避免捕获因其他原因而发生的异常.

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();
    }
Run Code Online (Sandbox Code Playgroud)

Jim*_*hel 40

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

所以如果你宣布locker为:

static object locker = new object();
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

只需一个电话:

File.AppendAllText(Filepath, text.ToString());
Run Code Online (Sandbox Code Playgroud)

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

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

class LogMessage
{
    public string Filepath { get; set; }
    public string Text { get; set; }
}

BlockingCollection<LogMessage> _logMessages = new BlockingCollection<LogMessage>();
Run Code Online (Sandbox Code Playgroud)

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

_logMessages.Add(new LogMessage("foo.log", "this is a test"));
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 谢谢,很通过。我喜欢我的大使用块的部分原因是我可以设置 FileShare.Read,我不确定 File.AppendAllText 的情况。但话又说回来,我想这只是我与其他流程竞争的一个因素。 (2认同)
  • 哦,先生你很棒。 (2认同)

Mat*_*ius 18

你也可以使用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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • ReaderWriterLock(和ReaderWriterLockSlim)适用于需要允许多个读者和一个作者的情况。在这种情况下,如果您只是从多个线程写入文件而没有读取,则建议锁定。 (3认同)
  • 刚刚看完这篇文章,它对我有帮助,谢谢@Matas!我想我应该通知一下,ReaderWriterLock 有一个后继者,称为 ReaderWriterLockSlim,请参阅 https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim(v=vs.110).aspx (2认同)