我有一个系统可以创建一些XmlReader
和XmlWriter
实例,将它们传递给客户端,这样客户端就可以编写和读取XML。
有时,多个线程访问系统:
XmlWriter
实例并开始编写XmlReader
实例,并开始阅读。显然,有时读者会在作者完成写作之前开始阅读,这会导致读者抛出异常:
System.Xml.XmlException:缺少根元素。
这并不特别令人惊讶,但有没有办法让这个系统线程安全?
MemoryStream
作为底层数据存储,但这似乎不起作用。我认为,上述任何一种方法都可以解决我的问题。并不要求写作和阅读同时发生。实际上,如果我能告诉客户请求XmlReader
读者尚未准备就绪,我会非常高兴,并且必须稍后再试。但是,这需要有一种方法来检测作者是否还在写作。
我可以完全控制创建XmlReader
和XmlWriter
实例的代码,但我无法更改公共API。我可以使用string
,StringBuilder
,MemoryStream
或任何其他内存对象来完成工作,作为底层存储,我可以使用任何派生类XmlReader
和XmlWriter
。
有没有办法让这个系统线程安全?
答案 0 :(得分:7)
您最好的选择可能是创建源自XmlReader
和XmlWriter
的自定义类。您可以向它们添加公共同步对象,并使用类似ReadWriterLockSlim
的内容来协调读取和写入。你必须在作家班中确定你说出来的意思是什么"完成" (即使它没有完全完成,也可以写完一个节点?等等)
然后,您可以在读取器块中创建方法,直到写入不在进行中。
答案 1 :(得分:3)
您正在寻找的是一个流,让一个线程写入而另一个线程读取。 .NET Framework不提供这样的功能。您可以使用命名管道来完成它,但我发现这很麻烦。所以我写了我称之为ProducerConsumerStream
的东西。它只是一个包含在Stream
接口中的循环队列。有关说明和完整来源,请参阅Building a new type of stream。
要与XmlWriter
和XmlReader
一起使用,您可以执行以下操作:
const int BufferSize = 1024 * 1024; // megabyte stream buffer
var pcStream = new ProducerConsumerStream(BufferSize);
// start producer thread, passing it the stream
// start consumer thread, passing it the stream
// Destroy the stream when the producer and consumer are done
生产者线程将创建一个写入流的XmlWriter
:
using (var writer = XmlWriter.Create(pcStream, writerSettings))
{
// write xml here
}
// when you're done writing XML, call CompleteAdding on the stream.
// This will let the reader know when the stream is ended.
pcStream.CompleteAdding();
读者线程类似:
using (var reader = XmlReader.Create(pcStream, readerSettings))
{
// read XML here
}
请确保在您的编写设置中设置CloseOutput = false
,并在阅读器设置中设置CloseInput = false
。否则,流可能会过早丢弃。
这里的关键是ProducerConsumerStream.Read
阻塞,直到它可以读取数据。除非通过调用CompleteAdding
来发信号通知流的结尾。
明白,这是一个内存结构。如果你想继续使用XML,你还必须以其他方式做到这一点。虽然我认为您可以从ProducerConsumerStream
派生并覆盖Write
方法,以便将其输出到文件以及将内容复制到内部缓冲区。
答案 2 :(得分:2)
听起来你需要类似于Java中的PipedReader / PipedWriter。 http://docs.oracle.com/javase/7/docs/api/java/io/PipedReader.html。看起来.NET没有类似的东西:Java's PipedReader / PipedWriter equivalents in C#?。
您可以通过使用此Java代码作为基础实现您自己的System.IO.TextWriter和System.IO.TextReader,从而在Java中执行相同的操作:
答案 3 :(得分:2)
是否可以让读者等到作者完成写作?
是hybrid thread synchronization construct,例如ManualResetEventSlim,如下:
internal class Program
{
private static void Main()
{
var readOperation = new ManualResetEventSlim();
var stream = new MemoryStream();
Task.Factory.StartNew(() =>
{
using (var writer = XmlWriter.Create(stream))
{
Console.WriteLine(
"Thread {0} is writing...",
Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(3000);
CreateXmlDocument(writer);
}
stream.Position = 0;
readOperation.Set();
});
Task.Factory.StartNew(() =>
{
readOperation.Wait();
using (var reader = XmlReader.Create(stream))
while (reader.Read())
Console.WriteLine(
"Thread {0} is reading...",
Thread.CurrentThread.ManagedThreadId);
});
Console.ReadKey();
}
}
控制台输出:
Thread 7 is writing...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
Thread 11 is reading...
CreateXmlDocument
函数(尽管它不是很有趣)定义为:
private static void CreateXmlDocument(XmlWriter writer)
{
writer.WriteStartDocument();
writer.WriteStartElement("Product");
writer.WriteAttributeString("ID", "001");
writer.WriteAttributeString("Name", "Soap");
writer.WriteElementString("Price", "10.00");
writer.WriteStartElement("OtherDetails");
writer.WriteElementString("BrandName", "X Soap");
writer.WriteElementString("Manufacturer", "X Company");
writer.WriteEndElement();
writer.WriteEndDocument();
}
答案 4 :(得分:0)
一个简单的想法是创建一个(可能是静态的)当前正在编写的文档字典。当该文档在该字典中时,读取请求被阻止或被拒绝。
答案 5 :(得分:-2)
好主意是在一个影响XMLWriter的锁中创建XML文件的副本。
作家代码:
lock(_WriteLock)
_MyXmlWriter.Flush();
读者代码:
lock(_WriteLock)
File.Copy(myXMLFilePath, tempXMLFilePath);
_MyXMLReader = new XMLReader(tempXMLFilePath);