正在后台写入文件

本文关键字:文件 后台 | 更新日期: 2023-09-27 18:25:45

给定一个写入文本文件的方法

public void WriteToFile( ) {
    var file = "C:''AsyncTest.txt";
    var writer = File.Exists( file ) ? File.AppendText( file ) : File.CreateText( file );
    writer.WriteLine( "A simulated entry" );
    writer.Close();
}

我需要模拟一个场景,在这个场景中,这个方法可以在循环中调用,可能会调用几十次,并且必须异步运行。

所以我试着在一个新的线程中调用这个方法,就像这样(其中writer是WriteToFile所在的类)

//in a loop...
  Thread thread = new Thread( writer.WriteToFile );
  thread.Start(  );

它只运行一次,但会引发IO异常,表明该文件正在由另一个进程在后续迭代中使用。事实上,这很有道理,但我不知道如何解决它。

我试着像这个一样使用Join()

Thread thread = new Thread( writer.WriteToFile );
thread.Start(  );
thread.Join();

但这会锁定调用线程,直到所有连接的线程完成,这有点违背目的,不是吗?

我尝试使用ThreadPool.QueueUserWorkItem(writer.WriteToFile);,但得到了相同的IO异常。

我试过使用锁定

private object locker = new object();
public void WriteToFile( ) {
  lock(locker){
    //same code as above
  }
}

但这对没有明显影响

我还尝试使用Task类,但没有成功。

那么,如何在不锁定调用线程的情况下,将这些后台线程"堆叠"到一个没有冲突的文件中呢?

正在后台写入文件

另一个选项是创建一个队列。让主线程将字符串放在队列中,并让持久后台线程读取队列并写入文件。这真的很容易做到。

private BlockingCollection<string> OutputQueue = new BlockingCollection<string>();
void SomeMethod()
{
    var outputTask = Task.Factory.StartNew(() => WriteOutput(outputFilename),
        TaskCreationOptions.LongRunning);
    OutputQueue.Add("A simulated entry");
    OutputQueue.Add("more stuff");
    // when the program is done,
    // set the queue as complete so the task can exit
    OutputQueue.CompleteAdding();
    // and wait for the task to finish
    outputTask.Wait();
}
void WriteOutput(string fname)
{
    using (var strm = File.AppendText(filename))
    {
        foreach (var s in OutputQueue.GetConsumingEnumerable())
        {
            strm.WriteLine(s);
            // if you want to make sure it's written to disk immediately,
            // call Flush. This will slow performance, however.
            strm.Flush();
        }
    }
}

后台线程在输出队列上进行非繁忙等待,因此它不使用CPU资源,除非它实际输出数据。因为其他线程只需要在队列中放入一些东西,所以基本上不需要等待。

有关更多信息,请参阅我的博客"简单多线程"第2部分。

您可以使用以下内容

// To enqueue the write
ThreadPool.QueueUserWorkItem(WriteToFile, "A simulated entry");
// the lock
private static object writeLock = new object();
public static void WriteToFile( object msg ) {
    lock (writeLock) {
        var file = "C:''AsyncTest.txt";
        // using (var writer = File.Exists( file ) ? File.AppendText( file ) : File.CreateText( file )) {
        // As written http://msdn.microsoft.com/it-it/library/system.io.file.appendtext(v=vs.80).aspx , File.AppendText will create the
        // file if it doesn't exist
        using (var writer = File.AppendText( file )) {
            writer.WriteLine( (string)msg );
        }
    }
}

请将using与文件一起使用!

您可以像尝试一样处理锁定,但需要包含using语句:

private readonly object _lockObj = new Object();
public void WriteToFile( )
{
    var file = "C:''AsyncTest.txt";
    lock (_lockObj)
    {
        using (StreamWriter writer = File.AppendText( file ))
        {
            writer.WriteLine( "A simulated entry" );
        }
    }
}

此外,您不需要利用CreateText,因为如果文件不存在,AppendText将创建该文件。最后,我以前也遇到过这个代码的问题,因为锁定将在Windows释放资源之前释放。这种情况很少见,但确实会发生,所以我只添加了一点重试逻辑来查找特定的异常。

  1. 处理您的流
  2. 使用TPL或异步/等待功能

例如:

Task.Run(() => File.WriteAllText("c:''temp'test.txt", "content"));

这将异步运行写操作,而不需要关心线程。

此外,流和流写入程序提供了WriteAsync方法,您可以使用这些方法并等待它们。

更新

为了避免"锁定"问题,请不要执行锁定操作:)如果您试图从不同的线程写入同一个文件,就会发生锁定。您可以使用File.Open()方法并指定模式,这样它就会阻塞线程并等待文件可写。

但是阻挡是不好的。因此,如果您想从多个线程进行编写,我建议您创建一个队列,并将您的编写任务放入该队列。您可以从多个线程安全地放入(使用ConcurrentQueue<T>)。然后,您在后台任务中使用这个队列,并将队列中的内容逐个写入文件。

就是这样:多个发布者,一个[文件编写]消费者,超级简单,不需要锁。