【问题标题】:How to access COM object within a BackgroundWorker?如何在 BackgroundWorker 中访问 COM 对象?
【发布时间】:2014-06-25 05:33:20
【问题描述】:

我有一个 WPF 应用程序,在主窗体中,允许用户选择几个文件 (Excel),然后单击一个按钮进行数据提取并将它们上传到数据库。一切正常。

现在我想实现一个忙碌指示器。

所以我所做的是,声明一个 BackgroundWorker 线程并将我的数据库上传(这需要时间)作为后台线程。当线程启动和完成时,相应地设置忙碌指示符。问题出在我的上传过程中,我访问剪贴板以打印一些消息。所以我遇到了以下明显的错误。

"Current thread must be set to single thread apartment (STA) mode before OLE calls can be made."

BackgroundWorker 默认为 MTA。那么解决这个问题的最佳方法是什么?

代码:

Public WithEvents BgWorker As BackgroundWorker = New BackgroundWorker()

 Private Sub MainWindow_Loaded() Handles Me.Loaded
    AddHandler BgWorker.DoWork, AddressOf ExtractData
    AddHandler BgWorker.RunWorkerCompleted, AddressOf BgWorker_RunWorkerCompleted        
End Sub

Private Sub btnExtract_Click(sender As Object, e As RoutedEventArgs)

    .....

    Try
        .....

       Me.busyIndicator.IsBusy = True
       BgWorker.RunWorkerAsync(Me.cmbFormats.SelectedItem.ToString.Trim())

    Catch ex As Exception
        Utility.Message.ErrorMessage(ex)
    End Try

End Sub

完成的事件:

Private Sub BgWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
    busyIndicator.IsBusy = False
End Sub

工作:

Private Sub ExtractData(sender As Object, e As DoWorkEventArgs)
    Dim exformat As IExtractor = New FormatFactory().CreateInstance(e.Argument.ToString())
    If (exformat.FeedToDb(filename)) Then
        Utility.Message.SuccessMessage("Successfully Extracted to database")
    Else
    End If
 End Sub

Utility.Message.SuccessMessage :

 Public Shared Sub SuccessMessage(msg As String)
    Dim M As New Text.StringBuilder
    M.AppendLine()
    M.AppendLine(msg)
    M.AppendLine()
    Clipboard.Clear() 'problem with MTA
    Clipboard.SetText(M.ToString)
    MsgBox(M.ToString, MsgBoxStyle.Information, "FF IT")
End Sub

【问题讨论】:

  • 只需将 SuccesMessage() 调用移至 RunWorkerCompleted 事件处理程序。使用 e.Result 报告成功状态。
  • 问题是我在从 FeedToDb 内部调用的其他方法中有更多这样的调用。

标签: .net wpf multithreading com sta


【解决方案1】:

问题是我在从 FeedToDb 内部调用的其他方法中有更多这样的调用

开始告诉你这是一个多么可怕的想法是相当困难的。试图专注于最严重的问题:

  • 使用 BackgroundWorker 的目的是运行需要时间执行的代码。它不会冻结您的用户界面,用户可以(希望)做其他有用的事情。如果没有你的程序,那么他可以,比如说,检查他的 Facebook 页面或启动纸牌。但是通过强迫他点击消息框流的确定按钮,这完全被破坏了。你把你的用户变成了你程序的奴隶,他很快就会厌倦这种待遇。

  • MsgBox() 显示一个对话框。一个对话框需要一个所有者,它位于另一个窗口之上。 MsgBox 是方便的功能,您不必明确谁是所有者。它自行排序,选择当前处于活动状态的窗口。但是有一个大问题,你在另一个线程上显示它。当 MsgBox 去寻找所有者时,它不会找到。 Windows 由特定线程拥有,而您的 BackgroundWorker 线程不拥有任何线程。因此它将作为所有者退回到桌面窗口。如果用户实际上继续与您的应用程序中的其他窗口进行交互,那将是一个问题,当您使用工作线程时应该是可能的。现在,在尝试显示自身的消息框窗口和用户正在使用的窗口之间发生了冲突。消息框窗口将丢失。它将在前景窗口之后结束。用户永远看不到它,甚至不知道它的存在。在他看来你的程序冻结了,他不知道如何解冻它,他永远不会考虑最小化他正在使用的窗口。

  • Clipboard.SetText() 方法调用已经强烈提示您知道您有 UI 实现问题。您已经发现您强迫您的用户(或您自己)通过窥视孔查看。提供用户可能想要保留的重要信息,但除了他可以制作到剪贴板的一次性副本之外,没有给他任何选项来保留它。实际上不起作用,他将无法足够快地按Ctrl + V。这总是很有效,很多,如果你以后给他机会查看信息会更好。 ListBox 是一种更好的方法。或者 RichTextBox,非常适合作为显示日志信息的一种方式。

好吧,够了,您可能正在寻找快速修复。从 SuccessMessage() 方法中解脱出来,知道它很尴尬并且在工作线程中使用时无法正常工作:

Public Shared Sub SuccessMessage(msg As String)
    If System.Threading.Thread.CurrentThread.GetApartmentState <> Threading.ApartmentState.STA Then Return
    '' etc...
End Sub

【讨论】:

  • 感谢我正在寻找的解释。我通过删除所有剪贴板方法来推进我的代码。我尝试这样做的唯一目的是学习如何做到这一点。我花了几个小时试图弄清楚发生了什么。这种解释清除了乌云。谢谢。
【解决方案2】:

您可以使用自定义对象调用BackgroundWorkerReportProgress,该对象指示您尝试向 GUI 提供什么样的反馈,例如消息。

您可以将当前的BackgroundWorker 存储在线程本地字段中,并让SuccessMessage 查找它并在设置时使用它的ReportProgress。如果您设法隐藏/包装实际的BackgroundWorker,则可以加分,这很容易,并且允许您在类似情况下将此模式与其他类型的对象一起使用。

如果工作人员的进度被转化为消息框,您应该想出一种更好的方式来通知用户,不需要交互或窃取焦点,例如被动日志窗口。

【讨论】:

    【解决方案3】:

    尝试在应用程序入口点上使用 STAThread 属性。请参阅以下link

    【讨论】:

      【解决方案4】:

      创建、应用属性并启动线程。

      System.Threading.Thread thread = new System.Threading.Thread(()=&gt;{/*My Work*/});

      应用 MTA 属性。

      thread.TrySetApartmentState(System.Threading.ApartmentState.MTA);

      通话开始

      thread.Start();

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-08-14
        • 2012-10-10
        • 2017-10-15
        • 2013-01-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-07
        相关资源
        最近更新 更多