以线程安全的方式写入文件 [英] Writing to file in a thread safe manner

查看:433
本文介绍了以线程安全的方式写入文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

写入Stringbuilder异步文件。这段代码需要控制一个文件,写一个流并释放它。它处理来自异步操作的请求,可能随时进入。

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



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



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

  public string Filepath {get;组; } 
private Object locker = new Object();

公共异步任务WriteToFile(StringBuilder文本)
{
int timeOut = 100;
秒表秒表=新的秒表();
stopwatch.Start();
while(true)
{
try
{
//等待资源空闲
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
{
//文件不可用,与其他类实例或应用程序冲突

if(stopwatch.ElapsedMilliseconds> timeOut)
{
//放弃。
break;
}
//等待并重试
等待Task.Delay(5);
}
stopwatch.Stop();


解决方案

很大程度上取决于你写作的频率。如果你很少写一些相对较少的文本,那么就使用一个静态锁,然后完成它。无论如何,这可能是您最好的选择,因为磁盘驱动器一次只能满足一个请求。假设你所有的输出文件都在同一个驱动器上(可能不是一个合理的假设,但是忍受着我),在应用程序级别锁定和在OS级别完成的锁定之间没有太大的区别。所以如果你声明 locker 为:

  static object locker = new object(); 

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



如果你想要这个东西是防弹的(至少是合理的),你不能摆脱捕捉异常。坏事情可能会发生。您必须 以某种方式处理异常。你在面对错误时所做的事情完全是另一回事。如果文件被锁定,您可能需要重试几次。如果您的路径或文件名错误,或磁盘已满或其他错误,您可能要杀死该程序。再次,这取决于你。但是你不能避免异常处理,除非你没有错误的程序崩溃。



顺便说一句,你可以取代所有这些代码:

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

$ / code>

通过一次调用:

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

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

另外一种方法是让线程将消息写入队列,并有一个专用的线程为队列提供服务。你将有一个 BlockingCollection 的消息和相关的文件路径。例如:

 类LogMessage 
{
public string Filepath {get;组; }
public string Text {get;组; }
}

BlockingCollection< LogMessage> _logMessages = new BlockingCollection< LogMessage>();

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

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

你开始一个长时间运行的后台任务,除了服务这个队列之外什么都不做:

  foreach(var msg in _logMessages.GetConsumingEnumerable())
{
//当然你会想要异常处理在这里
File.AppendAllText(msg.Filepath,msg.Text);



$ b $ p
$ b

您的潜在风险是线程创建消息的速度太快,导致队列因为消费者无法跟上而无限制地发展。在应用程序中是否存在真正的风险,只有你可以说。如果您认为这可能是一种风险,则可以在队列中放置最大大小(条目数),以便如果队列大小超过该值,则生产者将等到队列中有空间后才能添加。 p>

Writing Stringbuilder to file asynchronously. This code takes control of a file, writes a stream to it and releases it. It deals with requests from asynchronous operations, which may come in at any time.

The FilePath is set per class instances (so the lock Object is per instance), but there is potential for conflict since these classes may share FilePaths. That sort of conflict, as well as all other types from outside the class instance, would be dealt with with retries.

Is this code suitable for its purpose? Is there a better way to handle this that means less (or no) reliance on the catch and retry mechanic?

Also how do I avoid catching exceptions that have occurred for other reasons.

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();
    }

解决方案

How you approach this is going to depend a lot on how frequently you're writing. If you're writing a relatively small amount of text fairly infrequently, then just use a static lock and be done with it. That might be your best bet in any case because the disk drive can only satisfy one request at a time. Assuming that all of your output files are on the same drive (perhaps not a fair assumption, but bear with me), there's not going to be much difference between locking at the application level and the lock that's done at the OS level.

So if you declare locker as:

static object locker = new object();

You'll be assured that there are no conflicts with other threads in your program.

If you want this thing to be bulletproof (or at least reasonably so), you can't get away from catching exceptions. Bad things can happen. You must handle exceptions in some way. What you do in the face of error is something else entirely. You'll probably want to retry a few times if the file is locked. If you get a bad path or filename error or disk full or any of a number of other errors, you probably want to kill the program. Again, that's up to you. But you can't avoid exception handling unless you're okay with the program crashing on error.

By the way, you can replace all of this code:

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

With a single call:

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

Assuming you're using .NET 4.0 or later. See File.AppendAllText.

One other way you could handle this is to have the threads write their messages to a queue, and have a dedicated thread that services that queue. You'd have a BlockingCollection of messages and associated file paths. For example:

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

BlockingCollection<LogMessage> _logMessages = new BlockingCollection<LogMessage>();

Your threads write data to that queue:

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

You start a long-running background task that does nothing but service that queue:

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

Your potential risk here is that threads create messages too fast, causing the queue to grow without bound because the consumer can't keep up. Whether that's a real risk in your application is something only you can say. If you think it might be a risk, you can put a maximum size (number of entries) on the queue so that if the queue size exceeds that value, producers will wait until there is room in the queue before they can add.

这篇关于以线程安全的方式写入文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆