使用多线程进行慢速互操作

问题描述 投票:1回答:2

我有一个程序,当单击一个按钮时会创建两个pdf文件。它在WinForms中使用Microsoft Office interop,文件创建如下:

  1. 用户正在处理程序中的某些内容
  2. 单击按钮
  3. 程序根据其中带有书签的模板创建一个单词文件
  4. 写入书签及其表中
  5. 另存为pdf
  6. 关闭活动文档
  7. 关闭单词app
  8. 关闭子窗体并切换到另一窗体

*该应用程序一词对用户不可见

它本身可以很好地工作,但是完成两个文件需要8秒钟,因此我尝试使用多线程,这样用户就不必等待并且可以处理其他事情或继续执行步骤1再次。

但是,它会引发各种错误; COM,RPC等,即使在数据库连接中也是如此,我认为原因是因为有两个单独的线程在工作,并且使用相同的资源,所以在某个时间点上,另一个可能关闭了另一个线程的资源。正在/即将使用因此,我尝试使用join(),以便另一个线程可以先完成其工作,关闭其各自的资源,然后继续进行下一个。

这很好,如果用户没有单击该按钮来创建另一个文件,那么该文件将在另一个文件之后创建(如果用户完成第一步的速度比预期的快)

join()足以处理错误以及在后台处理文件创建,但是,我想处理在文件创建之后紧接着进行的情况,因为在这种情况下,然后,由于线程彼此等待,因此该过程似乎是线性的,除此之外,还会产生瓶颈,其中线程现在排成一行并阻塞了主线程,这使用户等待12秒或更长时间。

我想知道/需要澄清的是]

  1. 我可以创建使用interop的两个文件使用不同的资源,以免产生错误吗?
  2. 我可以使用join()还是有其他替代方法,其中它不阻塞UI线程或主线程?这样,文件创建线程仅在后台排队(彼此仍在等待完成),而无需用户等待。
  3. 我是否错误地使用了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#。任何帮助/指导,不胜感激。谢谢!

c# vb.net multithreading winforms office-interop
2个回答
0
投票
  1. 我不会一次使用多个线程中的任何单词/办公互操作。以我的经验,这些事情实际上是不够可靠的,没有引入可能的并发问题。但这可能取决于您到底在做什么。
  2. 线程和连接是处理多个线程的传统方法。较新的方法是使用Tasks and async/await。这可能有助于强制执行一致的顺序,而不会阻塞主线程。
  3. [通常应避免从主线程执行时会阻塞的任何方法,包括.Join()。

我会考虑使用生产者/消费者模式。当用户单击按钮时,主线程将生成要处理的文档,而后台线程将使用文档并进行处理。 blocking collection通常可以用作两个线程之间的接口。

另一种选择是limited concurrency task scheduler。这使您可以安排任务以进行处理,同时确保仅同时处理一个(或其他一些限制)。


0
投票

我的建议是创建一个具有嵌入式宏VBA程序的启用宏的模板,该程序执行所有书签并保存为PDF。这样可以将Interop调用减少为仅创建/处理Word应用程序并使用传入的参数运行VBA过程。

© www.soinside.com 2019 - 2024. All rights reserved.