Concurrent XmlReader and XmlWriter

本文关键字:XmlWriter and XmlReader Concurrent | 更新日期: 2023-09-27 17:59:50

我有一个系统,它创建一些XmlReaderXmlWriter实例,并将它们传递给客户端,以便客户端可以编写和读取XML。

有时,多个线程访问系统:

  1. 线程1获得一个XmlWriter实例并开始写入
  2. 线程2获得一个代表相同XML文档的XmlReader实例,并开始读取

显然,有时读者在作者完成写作之前就开始阅读,这会导致在读者中引发异常:

系统。Xml。XmlException:缺少根元素。

这并不特别令人惊讶,但有什么方法可以使这个系统线程安全吗?

  • 有可能让读者阅读,而作者写作吗?我尝试使用MemoryStream作为底层数据存储,但似乎不起作用
  • 是否可以让读者等待作者完成写作
  • 有可能检测到作者仍在写作吗

我相信,以上任何一种方法都能解决我的问题。写作和阅读不需要同时进行。事实上,如果我能简单地告诉请求XmlReader的客户,阅读器还没有准备好,必须稍后再试,我会非常高兴。然而,这需要有一种方法来检测作者是否仍在写作。

我可以完全控制创建XmlReaderXmlWriter实例的代码,但不能更改公共API。我可以使用stringStringBuilderMemoryStream或任何其他将完成任务的内存中对象作为底层存储,并且我可以使用任何派生的XmlReaderXmlWriter类。

有什么方法可以使这个系统线程安全吗?

Concurrent XmlReader and XmlWriter

您最好的选择可能是创建从XmlReaderXmlWriter派生的自定义类。您可以向它们添加一个通用的同步对象,并使用类似ReadWriterLockSlim的东西来协调读写。你必须在编写器类中确定当你说它"完成"时你的意思是什么(已处理?完成了对单个节点的编写,即使它还没有完全完成?等等)

然后,您可以在阅读器块中创建方法,直到没有进行编写。

您要寻找的是一个流,它允许一个线程写入,而另一个线程读取。这个NET Framework没有提供这样的东西。您可以使用命名管道来完成此操作,但我发现这很麻烦。所以我写了一个我称之为ProducerConsumerStream的东西。它只是一个封装在Stream接口中的循环队列。有关描述和完整源代码,请参见构建新类型的流。

要将其与XmlWriterXmlReader一起使用,您需要执行以下操作:

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方法,以便它输出到文件,并将内容复制到内部缓冲区。

听起来你需要一些类似于Java中的PipedReader/PipedWriter的东西。http://docs.oracle.com/javase/7/docs/api/java/io/PipedReader.html.看起来像。NET没有类似的东西:Java';s C#中的PipedReader/PipedWriter等效程序?。

您可以通过实现自己的系统来做与Java中相同的事情。IO.TextWriter和系统。IO.TextReader通过使用以下Java代码作为基础:

http://grepcode.com/file_/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/io/PipedReader.java/?v=source

http://grepcode.com/file_/repository.grepcode.com/java/root/jdk/openjdk/7-b147/java/io/PipedWriter.java/?v=source

是否可以让读者等待作者完成写作?

是,使用混合线程同步结构,例如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();
}

一个简单的想法是创建一个(可能是静态的)当前正在编写的文档字典。当该文档在该字典中时,读取请求要么被阻止,要么被拒绝。

好主意是在对XMLWriter也有影响的锁中创建XML文件的副本。

书写器代码:

lock(_WriteLock)
     _MyXmlWriter.Flush();

读卡器代码:

lock(_WriteLock)
     File.Copy(myXMLFilePath, tempXMLFilePath);
_MyXMLReader = new XMLReader(tempXMLFilePath);