OpenXML 在写入元素时挂起

2024-04-01

我有一个程序,它基本上从数据库中提取数据,将其缓存到文件中,然后将该数据导出为多种格式(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.

这是一个屏幕截图,显示了所有(在本例中为 8 个)并行任务被阻塞在OpenXmlWriter.WriteElement method:

完整堆栈对于这些挂起的线程之一 - 带注释。

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 个导出器在运行,则可能还会写入 1,000 个文件,但在任何给定时间只有 8 个文件正在积极写入。


我知道这个问题已经很老了,但这是 Microsoft 已知的 OpenXml-IsolatedFileStorage 问题。您可以在此处阅读有关解决方法的信息http://support.microsoft.com/kb/951731 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<WorksheetPart>();
var workSheetWriter = OpenXmlWriter.Create(new PackagePartStream(worksheetPart.GetStream()));
workSheetWriter.WriteStartElement(new Worksheet());
//rest of your code goes here ...
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

OpenXML 在写入元素时挂起 的相关文章

随机推荐