使用多线程进行慢速 Ms 互操作
Posted
技术标签:
【中文标题】使用多线程进行慢速 Ms 互操作【英文标题】:Using Multi-threading for Slow Ms Interop 【发布时间】:2020-09-17 01:08:56 【问题描述】:我有一个程序可以在单击按钮时创建两个 pdf 文件。它在 WinForms 中使用 Microsoft Office 互操作,文件创建过程如下;
-
用户在程序中处理某事
点击按钮
程序根据包含书签的模板创建 word 文件
写入书签及其表格
另存为 pdf 格式
关闭活动文档
关闭单词app
关闭子窗体并切换到另一个窗体
*单词 app 对用户不可见
它本身可以正常工作,但是完成这两个文件需要 8 秒,所以我尝试使用多线程,这样用户就不必等待,可以处理其他事情或继续执行步骤再 1 次。
但是,它会引发各种错误; COM、RPC 等,甚至在数据库连接中,我认为原因是因为有两个单独的线程在工作并且它使用相同的资源,所以某个时间点另一个可能关闭了另一个线程的资源正在使用/即将使用 所以我尝试使用join(),这样另一个线程可能会先完成它的工作,关闭其各自的资源,然后继续下一个。
这很好用,如果用户没有在另一个文件之后单击按钮来创建另一个文件(在用户完成第一步比预期更快的情况下)
join()
足以处理错误以及在后台处理文件创建,但是,我想处理文件创建紧随其后的情况,因为在这种情况下,然后看起来由于线程相互等待,进程是线性的,除了它会产生瓶颈,其中线程现在排队并阻塞主线程,这使用户等待 12 秒或更长时间。
我想知道/需要澄清的是;
-
我可以创建这两个使用
interop
的文件使用不同的资源,这样就不会产生错误吗?
我可以使用join()
的替代方法还是有替代方法,它不会阻塞 UI 线程或主线程?这样文件创建线程就在后台排队(仍然等待对方完成),而不会让用户等待。
我是否错误地使用了 join() 或最终消耗更多时间的线程?
这是我的代码;
用于创建文档的模块中的过程和公共变量:
Public tsThread As Thread
Public psThread As Thread
Sub SaveDoc(docType,someArgs)
Dim wordApp = New Word.Application
Dim templateBookmarks As Word.Bookmarks
Dim templateName As String
Dim template As New Word.Document
wordApp = CreateObject("Word.Application")
Select Case docType
Case "Type1"
templateName = "SampleType.docx"
template = wordApp.Documents.Add(templatePath & templateName)
templateBookmarks = template.Bookmarks
templateBookmarks.Item("bookmarkInWord").Range.Text = "Foo"
template.Tables(1).Cell(msWordRow, 1).Range.Text = "Value in cell 1"
Case "Type2"
‘Same thing, just different values and template
Case "Type3"
End select
template.SaveAs2(savePath & saveName, Word.WdSaveFormat.wdFormatPDF)
wordApp.ActiveDocument.Close(Word.WdSaveOptions.wdDoNotSaveChanges)
wordApp.Quit()
End Sub
单击按钮时子窗体中的过程:
If Not IsNothing(tsThread) Then
If tsThread.IsAlive Then
tsThread.Join()
End If
End If
If Not IsNothing(psThread) Then
If psThread.IsAlive Then
psThread.Join()
End If
End If
tsThread = New Thread(Sub() SaveDoc(docType1,someArgs))
tsThread.Start()
If Not IsNothing(tsThread) Then
If tsThread.IsAlive Then
tsThread.Join()
End If
End If
psThread = New Thread(Sub() SaveDoc(docType2,someArgs))
psThread.Start()
If records = maxRecords Then
If psThread.IsAlive or tsThread.isAlive Then
tsThread.Join()
psThread.Join()
Dim fooThread = New Thread(Sub() SaveDoc(docType3,someArgs))
fooThread.Start()
End If
End If
SwitchForm("Child Form for Step 1")
End Sub
没有使用join()
的错误通常出现在这些代码部分:
wordApp.ActiveDocument.Close(Word.WdSaveOptions.wdDoNotSaveChanges)
或在
wordApp.Quit()
或在书签中
templateBookmarks.Item("bookmarkInWord").Range.Text = "Foo"
或者有时在不同时间或数据库连接过程中的一个案例中的任何地方。
我对多线程以及 Interop 女士还是比较陌生,所以如果我有任何误解,我会寻求帮助。代码在 VB 中,但我也可以理解 C#。非常感谢任何帮助/指导。谢谢!
【问题讨论】:
你打电话给wordApp = New Word.Application
和wordApp = CreateObject("Word.Application")
有什么原因吗?
【参考方案1】:
-
我不会尝试一次使用来自多个线程的任何 word/office 互操作。以我的经验,这些事情本身就足够不可靠,而不会引入可能的并发问题。但这可能取决于您到底在做什么。
线程和连接是处理多线程的相当老派的方法。较新的方法是使用Tasks and async/await。这可能有助于强制执行一致的排序,而不会阻塞主线程。
您通常应避免在从主线程执行时可能阻塞的任何方法,包括 .Join()。
我会考虑使用生产者/消费者模式。当用户单击按钮时,主线程将生成要处理的文档,而后台线程将消耗文档并进行处理。 blocking collection 通常可以用作两个线程之间的接口。
另一种选择是limited concurrency task scheduler。这使您可以安排任务进行处理,同时确保同时处理一个(或其他一些限制)。
【讨论】:
【参考方案2】:我的建议是使用嵌入的 VBA 程序创建一个启用宏的模板,该程序执行所有书签和保存到 PDF。这将 Interop 调用减少为仅创建/处理单词 application 并使用传入的参数运行 VBA 过程。
【讨论】:
以上是关于使用多线程进行慢速 Ms 互操作的主要内容,如果未能解决你的问题,请参考以下文章