合并 XPS 文件将打开的文件句柄留给文档引用
Merging XPS files leaves open file handles to document references
我正在通过 IIS Web 应用程序合并 XPS 文件,但在合并过程中,文档引用文件句柄保持打开状态。在 Windows WPF 或控制台应用程序中,我不会担心句柄,因为它们会在应用程序关闭后被释放。回收应用程序池也将有效地关闭句柄。但是,由于缓存和性能的原因,我不能仅仅为了关闭文件句柄而回收应用程序池。
有没有办法在合并完成后关闭这些文件句柄?我尝试了几种不同的合并方法,但以下代码给了我最好的结果:
private DocumentPaginator CreateMergedDocument(IEnumerable<string> xpsFiles) {
using ( MemoryStream mergedStream = new MemoryStream() ) {
using ( var pkg = Package.Open( mergedStream, FileMode.Create, FileAccess.ReadWrite ) ) {
var pack = "pack://merged.xps";
var uri = new Uri( pack, UriKind.Absolute );
PackageStore.AddPackage( uri, pkg );
using ( XpsDocument mergedDocument =
new XpsDocument( pkg, CompressionOption.Maximum, pack ) ) {
FixedDocumentSequence seqNew = new FixedDocumentSequence();
foreach ( string sourceDocument in xpsFiles ) {
using ( XpsDocument xpsOld =
new XpsDocument( sourceDocument, FileAccess.Read ) ) {
FixedDocumentSequence seqOld = xpsOld.GetFixedDocumentSequence();
foreach ( DocumentReference dr in seqOld.References ) {
DocumentReference newDocumentReference = new DocumentReference();
newDocumentReference.Source = dr.Source;
( newDocumentReference as IUriContext ).BaseUri =
( dr as IUriContext ).BaseUri;
seqNew.References.Add( newDocumentReference );
}
}
}
XpsDocumentWriter xpsWriter = XpsDocument.CreateXpsDocumentWriter( mergedDocument );
xpsWriter.Write( seqNew );
PackageStore.RemovePackage( uri );
return seqNew.DocumentPaginator;
}
}
}
}
我找到了打开 xpsWriter.Write(seqNew)
代码行的文件句柄。这是预期的,因为它需要加载文件以复制到新的 FixedDocumentSequence
。但是,如果 XpsDocumentWriter
是一次性的并清理其非托管 resources/file 句柄,那就太好了。
在我的研究过程中,我尝试了一些其他的方法。我尝试过的一种方法是将每个页面视觉对象写入 SerializerWriterCollator
。我研究过的另一种方法是处理 FixedPage 内容并将 URI 更新为复制的图像和字体流,然后将原始 XAML 写回 XmlWriter
。两者都没有很好地工作。使用页面视觉效果和 SerializerWriterCollator
,我在合并后的 XPS 上得到的页面大小不正确,而且它正在切断内容。使用原始 XmlWriter
方法时,合并后的 XPS 中的一些图像变得混乱,并且错误地显示了一些页面内容。
经过更多研究,我发现用于 运行 我在 STA 线程中的函数的原始代码没有正确释放文件句柄和非托管资源。我们有一个自定义线程 class,带有调用操作的自定义实现,并 运行 加入线程。操作方法完成后线程未正确退出。为了解决这个问题,我将我的代码包装在一个类似于这样的块中:
private static readonly TaskScheduler _staScheduler = new StaTaskScheduler( 1 );
[Route( "merge" )]
[HttpGet]
public async Task<HttpResponseMessage> MergeXps() {
var paginator = await Task<DocumentPaginator>.Factory.StartNew(
() =>
{
var xpsFiles = Directory.GetFiles( "C:\Xps", "*.xps" );
var documentPaginator = CreateMergedDocument( xpsFiles );
return documentPaginator;
},
CancellationToken.None,
TaskCreationOptions.None,
_staScheduler );
var response = new HttpResponseMessage();
response.Content = new StringContent( $"Merged Document Page Count: {paginator.PageCount}" );
response.StatusCode = HttpStatusCode.OK;
return response;
}
我正在通过 IIS Web 应用程序合并 XPS 文件,但在合并过程中,文档引用文件句柄保持打开状态。在 Windows WPF 或控制台应用程序中,我不会担心句柄,因为它们会在应用程序关闭后被释放。回收应用程序池也将有效地关闭句柄。但是,由于缓存和性能的原因,我不能仅仅为了关闭文件句柄而回收应用程序池。
有没有办法在合并完成后关闭这些文件句柄?我尝试了几种不同的合并方法,但以下代码给了我最好的结果:
private DocumentPaginator CreateMergedDocument(IEnumerable<string> xpsFiles) {
using ( MemoryStream mergedStream = new MemoryStream() ) {
using ( var pkg = Package.Open( mergedStream, FileMode.Create, FileAccess.ReadWrite ) ) {
var pack = "pack://merged.xps";
var uri = new Uri( pack, UriKind.Absolute );
PackageStore.AddPackage( uri, pkg );
using ( XpsDocument mergedDocument =
new XpsDocument( pkg, CompressionOption.Maximum, pack ) ) {
FixedDocumentSequence seqNew = new FixedDocumentSequence();
foreach ( string sourceDocument in xpsFiles ) {
using ( XpsDocument xpsOld =
new XpsDocument( sourceDocument, FileAccess.Read ) ) {
FixedDocumentSequence seqOld = xpsOld.GetFixedDocumentSequence();
foreach ( DocumentReference dr in seqOld.References ) {
DocumentReference newDocumentReference = new DocumentReference();
newDocumentReference.Source = dr.Source;
( newDocumentReference as IUriContext ).BaseUri =
( dr as IUriContext ).BaseUri;
seqNew.References.Add( newDocumentReference );
}
}
}
XpsDocumentWriter xpsWriter = XpsDocument.CreateXpsDocumentWriter( mergedDocument );
xpsWriter.Write( seqNew );
PackageStore.RemovePackage( uri );
return seqNew.DocumentPaginator;
}
}
}
}
我找到了打开 xpsWriter.Write(seqNew)
代码行的文件句柄。这是预期的,因为它需要加载文件以复制到新的 FixedDocumentSequence
。但是,如果 XpsDocumentWriter
是一次性的并清理其非托管 resources/file 句柄,那就太好了。
在我的研究过程中,我尝试了一些其他的方法。我尝试过的一种方法是将每个页面视觉对象写入 SerializerWriterCollator
。我研究过的另一种方法是处理 FixedPage 内容并将 URI 更新为复制的图像和字体流,然后将原始 XAML 写回 XmlWriter
。两者都没有很好地工作。使用页面视觉效果和 SerializerWriterCollator
,我在合并后的 XPS 上得到的页面大小不正确,而且它正在切断内容。使用原始 XmlWriter
方法时,合并后的 XPS 中的一些图像变得混乱,并且错误地显示了一些页面内容。
经过更多研究,我发现用于 运行 我在 STA 线程中的函数的原始代码没有正确释放文件句柄和非托管资源。我们有一个自定义线程 class,带有调用操作的自定义实现,并 运行 加入线程。操作方法完成后线程未正确退出。为了解决这个问题,我将我的代码包装在一个类似于这样的块中:
private static readonly TaskScheduler _staScheduler = new StaTaskScheduler( 1 );
[Route( "merge" )]
[HttpGet]
public async Task<HttpResponseMessage> MergeXps() {
var paginator = await Task<DocumentPaginator>.Factory.StartNew(
() =>
{
var xpsFiles = Directory.GetFiles( "C:\Xps", "*.xps" );
var documentPaginator = CreateMergedDocument( xpsFiles );
return documentPaginator;
},
CancellationToken.None,
TaskCreationOptions.None,
_staScheduler );
var response = new HttpResponseMessage();
response.Content = new StringContent( $"Merged Document Page Count: {paginator.PageCount}" );
response.StatusCode = HttpStatusCode.OK;
return response;
}