簡體   English   中英

並發XmlReader和XmlWriter

[英]Concurrent XmlReader and XmlWriter

我有一個系統創建一些XmlReaderXmlWriter實例, 並將它們傳遞給客戶端 ,以便客戶端可以編寫和讀取XML。

有時,多個線程訪問系統:

  1. 線程1獲取XmlWriter實例並開始編寫
  2. 線程2獲取一個表示相同XML文檔的XmlReader實例,並開始讀取。

顯然,有時讀者會在作者完成寫作之前開始閱讀,這會導致讀者拋出異常:

System.Xml.XmlException:缺少根元素。

這並不特別令人驚訝,但有沒有辦法讓這個系統線程安全?

  • 在作者寫作是否可以讓讀者閱讀? 我嘗試使用MemoryStream作為底層數據存儲,但這似乎不起作用。
  • 是否有可能讓讀者等到作者完成寫作?
  • 是否有可能檢測到作者仍在寫作?

我認為,上述任何一種方法都可以解決我的問題。 並不要求寫作和閱讀同時發生。 實際上,如果我能告訴客戶端請求XmlReader讀者尚未准備就緒,我會非常高興,並且必須稍后再試。 但是,這需要有一種方法來檢測作者是否還在寫作。

我完全控制了創建XmlReaderXmlWriter實例的代碼,但我無法更改公共API。 我可以使用stringStringBuilderMemoryStream或任何其他內存對象來完成工作,作為底層存儲,我可以使用任何派生類的XmlReaderXmlWriter

有沒有辦法讓這個系統線程安全?

最好的辦法是創建源自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
}

確保在CloseInput = false器設置中設置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沒有類似的東西: C#中的Java的PipedReader / PipedWriter等價物?

您可以通過使用此Java代碼作為基礎實現您自己的System.IO.TextWriter和System.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);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM