2017-04-24 157 views
6

我想將大型XML文檔加載到XDocument對象中。 使用XDocument.Load(path, loadOptions)的簡單同步方法效果很好,但是在加載大文件(特別是從網絡存儲)時在GUI上下文中阻塞了很長時間。異步加載XDocument

我寫了這個異步版本,旨在提高文檔加載的響應速度,特別是通過網絡加載文件時。

public static async Task<XDocument> LoadAsync(String path, LoadOptions loadOptions = LoadOptions.PreserveWhitespace) 
    { 
     String xml; 

     using (var stream = File.OpenText(path)) 
     { 
      xml = await stream.ReadToEndAsync(); 
     } 

     return XDocument.Parse(xml, loadOptions); 
    } 

但是,在從本地磁盤加載的200 MB XML原始文件中,同步版本會在幾秒鐘內完成。異步版本(在32位的上下文中運行),而不是將引發OutOfMemoryException

at System.Text.StringBuilder.ToString() 
    at System.IO.StreamReader.<ReadToEndAsyncInternal>d__62.MoveNext() 

我想象這是因爲用於在內存中保留原始XML由XDocument解析臨時字符串變量。假設在同步場景中,XDocument.Load()能夠通過源文件進行流式傳輸,並且從不需要創建一個巨大的字符串來保存整個文件。

有沒有什麼辦法讓兩全其美?加載XDocument完全異步I/O,而不需要創建一個大的臨時字符串?

+0

也許你應該使用'XDocument.Load(流)'? – DavidG

+0

這會如何使加載操作異步? – Hydrargyrum

+0

那麼它本身不會,但它會消除你在這裏的字符串變量,並希望OOM異常。 – DavidG

回答

2

首先,任務不是異步運行的。您需要使用內置的異步IO命令或自己啓動線程池上的任務。例如

public static async Task<XDocument> LoadAsync 
(String path 
, LoadOptions loadOptions = LoadOptions.PreserveWhitespace 
) 
{ 
    return Task.Run(()=>{ 
    using (var stream = File.OpenText(path)) 
     { 
      return XDocument.Load(stream, loadOptions); 
     } 
    }); 
} 

如果您使用Parse的stream version,那麼您不會得到一個臨時字符串。

+3

好的。這就是我在對問題的最終評論中所概述的內容。因此,這將使用線程池線程來驅動隱式所需的I/O,因爲XDocument在流中咀嚼。而且I/O本身會零星地阻塞Task的工作線程。 看起來這是最好的,可以做到的,因爲沒有一個真正的XDocument.LoadAsync()實現,它使用適當的異步I/O指令。 雖然我沒有看到明確調用File.OpenText的好處。也可以直接調用XDocument.Load(path) – Hydrargyrum

+0

如果你正在並行讀取服務器上數以千計的XDocuments 10s,你可能會擔心從線程池中竊取線程而不是使用真正的異步IO,但這真的是一個問題? – bradgonesurfing

+1

可能不是。因此,我的評論是這可能夠好。無論如何我都贊成並接受 – Hydrargyrum