我有一个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
DoWork的:
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
答案 0 :(得分:4)
问题是我在FeedToDb内部调用的其他方法中有更多这些调用
开始告诉你这是多么可怕的想法是相当困难的。试着关注最严重的问题:
使用BackgroundWorker的目的是运行需要时间执行的代码。它不会冻结您的用户界面,用户可以(希望)做一些有用的事情。如果没有你的程序,那么他可以,比如,检查他的Facebook页面或点燃Solitaire。但是通过强迫他点击消息框流的OK按钮,这完全毁了。你把你的用户变成了你的程序的奴隶,他很快就厌倦了治疗。
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
答案 1 :(得分:2)
您可以使用自定义对象调用BackgroundWorker
的{{1}},该对象指示您尝试向GUI提供的反馈类型,例如消息。
您可以将当前ReportProgress
存储在线程本地字段中,并使BackgroundWorker
查找并在设置时使用SuccessMessage
。如果你设法隐藏/包装实际的ReportProgress
,额外的点,它很容易,它允许你在类似的情况下使用这种模式与其他类型的对象。
如果将工作人员的进度转换为消息框,您应该想出一种更好的方法来通知用户不需要交互或窃取焦点,例如被动日志窗口。
答案 2 :(得分:1)
尝试在应用程序入口点上使用STAThread属性。请参阅以下link。
答案 3 :(得分:1)
创建,应用属性并启动一个帖子。
System.Threading.Thread thread = new System.Threading.Thread(()=>{/*My Work*/});
应用MTA属性。
thread.TrySetApartmentState(System.Threading.ApartmentState.MTA);
致电开始
thread.Start();