给定一个写入文本文件的方法
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类无济于事。
那么如何在不冲突的情况下“堆叠”这些后台线程来写入单个文件,而不是锁定调用线程?
答案 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)
例如:
Task.Run(() => File.WriteAllText("c:\\temp\test.txt", "content"));
这将以异步方式运行写操作,而无需您处理线程。
此外,流和流编写器提供了可以使用和等待的WriteAsync方法。
<强>更新强>
为避免“锁定”问题,根本不做锁定:) 如果您尝试从不同的线程写入同一文件,则会发生锁定。您可以使用File.Open()方法并指定模式,以便它将阻塞线程并等待文件可写。
但阻止是坏事。
所以我建议你,如果你想从多个线程编写,创建一个队列并将你的写作任务放入这个队列。您可以安全地从多个线程(使用ConcurrentQueue<T>
)。
然后,您在后台任务中使用此队列,并将您在队列中的内容写入您的文件 - 逐个项目。
就是这样:多个出版商,一个[文件撰写]消费者,超级简单,无需锁定。