【问题标题】:Writing to a file in the background在后台写入文件
【发布时间】:2013-08-01 12:32:35
【问题描述】:

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

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

那么我怎样才能“堆叠”这些后台线程以写入单个文件而不会发生冲突,同时又不会锁定调用线程?

【问题讨论】:

  • 尝试在线程内部锁定一个全局(静态)变量,并在锁定结束之前(如 100 毫秒)添加一个轻微的睡眠(Windows 问题与打开和关闭文件)。
  • 一个文件还是多个文件?你要写的是固定的还是作为参数传递的?
  • stackoverflow.com/questions/10519853/… 这对解锁文件有帮助吗?
  • 使用任务。然后您可以使用ContinueWith 来排队下一个操作,依此类推。
  • 您打开文件的方式不可以异步写入同一个文件。

标签: c# .net io


【解决方案1】:

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

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 了解更多信息。

【讨论】:

  • 你不知道吗。我得到了锁,我的团队认为锁是坏的,想要排队……:)(很棒的博客,顺便说一句!)
  • @Forty-2:很高兴你成功了。谢谢你的好话。虽然我同意锁可能被滥用,但“锁不好”的口头禅可能会被带到不切实际的极端。见我的博客Unwise conventional wisdom, Part 1: Locks are slow
【解决方案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

【讨论】:

  • 嘿,谢谢。这行得通——至少在这个模拟中——希望它能转化为实际的应用程序:)。但是,如果锁定对象和写入方法是静态的,您是否介意解释为什么它可以工作,但如果不是(如 TheSolution 的答案)则失败?
  • 顺便说一句,不需要条件。 File.AppendText 将创建一个新文件,如果一个不存在。所以你可以写using (var writer = File.AppendText(file))
【解决方案3】:

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

【讨论】:

  • 你为什么在这里使用volatile?我会认为_lockObj 应该是readonly
  • @ken,你说对了。老实说,我不确定。也许我需要再来一杯咖啡。
【解决方案4】:
  1. 处置您的信息流。
  2. 使用 TPL 或异步/等待功能。

例如:

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

这将异步运行写入操作,无需您处理线程。

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

更新

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

但是阻塞是不好的。 所以我建议你,如果你想从多个线程写入,创建一个队列并将你的写入任务放入这个队列。您可以从多个线程安全地放置(使用ConcurrentQueue&lt;T&gt;)。 然后,您在后台任务中使用此队列,并将队列中的内容写入文件 - 一项一项。

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

【讨论】:

  • 你没有解决他的锁定问题
  • 如果您处理您的流(并且 .WriteAllText 在内部进行)并且您不同时从不同的文件写入同一个文件,您不会遇到任何锁定问题线程。
  • 如果您确实想从 diff 线程中写入相同的文件,我建议您不要锁定。将更新我的答案。
  • 抱歉,应该提到我正在使用 .NET 4.0 -- 我认为 Task.Run 是在 4.5 中引入的?
  • @AlexeyRaga You may use File.Open() methods and specify the mode so it will block a thread and wait until file is writable. 我认为你做不到(仅使用 File.Open
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-28
  • 1970-01-01
  • 2017-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多