在后台写入文件

时间:2013-08-01 12:32:36

标签: c# .net io

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

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类无济于事。

那么如何在不冲突的情况下“堆叠”这些后台线程来写入单个文件,而不是锁定调用线程?

4 个答案:

答案 0 :(得分:11)

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

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资源,除非它实际输出数据。而且因为其他线程只需要在队列中放置一些东西,所以基本上没有等待。

请参阅我的博客Simple Multithreading, Part 2以获取更多信息。

答案 1 :(得分:4)

您可以使用以下内容:

// 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与文件一起使用!

答案 2 :(得分:2)

您可以像尝试一样处理锁定,但需要包含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发布资源之前会释放锁。它很少见,但它发生了,所以我只是添加一个小的重试逻辑来寻找特定的异常。

答案 3 :(得分:2)

  1. 处理你的溪流。
  2. 使用TPL或async / await功能。
  3. 例如:

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

    这将以异步方式运行写操作,而无需您处理线程。

    此外,流和流编写器提供了可以使用和等待的WriteAsync方法。

    <强>更新

    为避免“锁定”问题,根本不做锁定:) 如果您尝试从不同的线程写入同一文件,则会发生锁定。您可以使用File.Open()方法并指定模式,以便它将阻塞线程并等待文件可写。

    但阻止是坏事。 所以我建议你,如果你想从多个线程编写,创建一个队列并将你的写作任务放入这个队列。您可以安全地从多个线程(使用ConcurrentQueue<T>)。 然后,您在后台任务中使用此队列,并将您在队列中的内容写入您的文件 - 逐个项目。

    就是这样:多个出版商,一个[文件撰写]消费者,超级简单,无需锁定。