4

Given a method that writes to a text file

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

I need to simulate a scenario in which this method could be called in a loop, possibly dozens of times and must run asynchronously.

So I tried calling the method in a new thread like so (where writer is the class where WriteToFile lives)

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

Which works perfectly once, but throws an IO Exception that the file is being used by another process on subsequent iterations. Which makes perfect sense, actually, but I don't know how to work around it.

I tried using Join() like this

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

But that locks the calling thread until all the joined threads complete, which sort of defeats the purpose, no?

I tried using ThreadPool.QueueUserWorkItem(writer.WriteToFile);, but get the same IO exception.

I tried using a lock

private object locker = new object();

public void WriteToFile( ) {
  lock(locker){
    //same code as above
  }
}

But that had no apparent effect

I also tried using the Task class to no avail.

So how can I "stack up" these background threads to write to a single file without conflict, while not locking up the calling thread?

4

4 回答 4

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,第 2 部分。

于 2013-08-01T13:56:28.717 回答
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与文件一起使用!

于 2013-08-01T12:42:44.760 回答
2
  1. 处理您的流。
  2. 使用 TPL 或 async/await 功能。

例如:

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

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

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

更新

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

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

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

于 2013-08-01T12:48:46.570 回答
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 释放资源之前被释放。这种情况很少见,但它会发生,所以我只是添加了一些重试逻辑来寻找特定的异常。

于 2013-08-01T12:35:34.123 回答