OpenXML在写元素时挂起

我有一个程序,它基本上从数据库中提取数据,将其缓存到一个文件,然后将该数据导出为多种格式(Excel,Excel 2003,CSV)。 我正在使用OpenXML SDK 2.0来完成Excel工作。 这些导出过程并行运行(使用Parallel.ForEach ),数据量可能非常大 – 例如,某些CSV为800MB。 在这些较大的导出期间,我注意到XML文档的编写将会挂起。 例如,如果我有8个并行导出,那么在某些时候它们都会“暂停”。 它们都围绕着同一点:

 //this.Writer is an OpenXmlWriter which was created from a WorksheetPart. this.Writer.WriteElement(new Cell() { CellValue = new CellValue(value), DataType = CellValues.String }); 

当发生这种情况时,我暂停调试器(在这种情况下是VS2013)并注意到所有线程都在代码的相同部分阻塞 – 有些在OpenXML SDK中更深一些 – 但它们都源于对OpenXmlWriter.WriteElement的调用。

我使用JustDecompile挖掘了源代码,但没有找到任何答案。 似乎正在使用中间流写入隔离存储,这由于某种原因阻塞。 每个基础流都是FileStream

这是一个屏幕截图,显示在OpenXmlWriter.WriteElement方法中或内部阻止的所有(在本例中为8个)并行任务:

红!

其中一个挂起线程的完整堆栈 – 带注释。

 WindowsBase.dll!MS.Internal.IO.Packaging.PackagingUtilities.CreateUserScopedIsolatedStorageFileStreamWithRandomName Normal WindowsBase.dll!MS.Internal.IO.Packaging.PackagingUtilities.CreateUserScopedIsolatedStorageFileStreamWithRandomName(int retryCount, out string fileName) WindowsBase.dll!MS.Internal.IO.Packaging.SparseMemoryStream.EnsureIsolatedStoreStream() //---> Why are we writing to isolated storage at all? WindowsBase.dll!MS.Internal.IO.Packaging.SparseMemoryStream.SwitchModeIfNecessary() WindowsBase.dll!MS.Internal.IO.Zip.ZipIOFileItemStream.Write(byte[] buffer, int offset, int count) System.dll!System.IO.Compression.DeflateStream.WriteDeflaterOutput(bool isAsync) System.dll!System.IO.Compression.DeflateStream.Write(byte[] array, int offset, int count) WindowsBase.dll!MS.Internal.IO.Packaging.CompressStream.Write(byte[] buffer, int offset, int count) WindowsBase.dll!MS.Internal.IO.Zip.ProgressiveCrcCalculatingStream.Write(byte[] buffer, int offset, int count) WindowsBase.dll!MS.Internal.IO.Zip.ZipIOModeEnforcingStream.Write(byte[] buffer, int offset, int count) System.Xml.dll!System.Xml.XmlUtf8RawTextWriter.FlushBuffer() System.Xml.dll!System.Xml.XmlUtf8RawTextWriter.WriteAttributeTextBlock(char* pSrc, char* pSrcEnd) System.Xml.dll!System.Xml.XmlUtf8RawTextWriter.WriteString(string text) System.Xml.dll!System.Xml.XmlWellFormedWriter.WriteString(string text) DocumentFormat.OpenXml.dll!DocumentFormat.OpenXml.OpenXmlElement.WriteAttributesTo(System.Xml.XmlWriter xmlWriter) DocumentFormat.OpenXml.dll!DocumentFormat.OpenXml.OpenXmlElement.WriteTo(System.Xml.XmlWriter xmlWriter) DocumentFormat.OpenXml.dll!DocumentFormat.OpenXml.OpenXmlPartWriter.WriteElement(DocumentFormat.OpenXml.OpenXmlElement elementObject) //---> At this point, threads seem to be blocking. MyProject.Common.dll!MyProject.Common.Export.ExcelWriter.WriteLine(string[] values) Line 117 

值得一提的另一件事是,虽然有8件事(在这种情况下)被一次导出,但每个出口商都在写入许多串联的文件。 例如,给定的导出可能有150个导出到的底层文件 – 输入数据被分段,只有一部分写入每个文件。 基本上,我从数据库缓存批量数据,然后读取一行并将其(按顺序 – 逐个)推送到应包含此数据的流。 关键是如果有8个出口商正在运行,那么可能有1000个文件也被写入,但在任何给定时间只有8个正在写入。

我知道这个问题已经过时了,但这与OpenXml-IsolatedFileStorage的微软问题有关。 您可以在http://support.microsoft.com/kb/951731上阅读有关解决方法的信息:

IsolatedStorageFile类不是线程安全的,IsolatedStorageFile是静态的,并且在所有PackagePart对象之间共享。 因此,当访问使用IsolatedStorageFile对象来缓冲数据的多个PackagePart流进行写入(包括刷新)时,IsolatedStorageFile类中的线程安全问题会暴露,从而导致死锁。

基本思想是包装一个PackagePart流并锁定写入它。 他们用包裹的流指出了一个例子。 这是实施:

 public class PackagePartStream : Stream { private readonly Stream _stream; private static readonly Mutex Mutex = new Mutex(false); public PackagePartStream(Stream stream) { _stream = stream; } public override long Seek(long offset, SeekOrigin origin) { return _stream.Seek(offset, origin); } public override void SetLength(long value) { _stream.SetLength(value); } public override int Read(byte[] buffer, int offset, int count) { return _stream.Read(buffer, offset, count); } public override void Write(byte[] buffer, int offset, int count) { Mutex.WaitOne(Timeout.Infinite, false); _stream.Write(buffer, offset, count); Mutex.ReleaseMutex(); } public override bool CanRead { get { return _stream.CanRead; } } public override bool CanSeek { get { return _stream.CanSeek; } } public override bool CanWrite { get { return _stream.CanWrite; } } public override long Length { get { return _stream.Length; } } public override long Position { get { return _stream.Position; } set { _stream.Position = value; } } public override void Flush() { Mutex.WaitOne(Timeout.Infinite, false); _stream.Flush(); Mutex.ReleaseMutex(); } public override void Close() { _stream.Close(); } protected override void Dispose(bool disposing) { _stream.Dispose(); } } 

用法示例:

 var worksheetPart = document.WorkbookPart.AddNewPart(); var workSheetWriter = OpenXmlWriter.Create(new PackagePartStream(worksheetPart.GetStream())); workSheetWriter.WriteStartElement(new Worksheet()); //rest of your code goes here ...