我正在尝试给一些用户娱乐,并在加载单独的复杂窗口期间显示“请等待”窗口,使用Marquee。我试图通过在新线程中加载Window来实现这一点,如下所示:
Public Function ShowPleaseWait() As System.Threading.Thread
Dim PleaseWait As New System.Threading.Thread(AddressOf LoadPleaseWait)
PleaseWait.SetApartmentState(System.Threading.ApartmentState.STA)
PleaseWait.IsBackground = True
PleaseWait.Start()
Return PleaseWait
End Function
Public Sub LoadPleaseWait()
Dim window As New windowPleaseWait
Try
window.Show()
System.Windows.Threading.Dispatcher.Run()
Catch e As System.Threading.ThreadAbortException
window.Close()
window = Nothing
End Try
End Sub
在调用代码中,它调用ShowPleaseWait
并保存线程以供稍后使用。要关闭窗口,它会在保存的线程上调用Thread.Abort
。这反过来会导致它进入Catch
。无论有没有捕获,我都尝试过许多不同的方法。
第一次调用时,这非常有效。但是,window.Show()
的其他来电将失败,例外情况为The calling thread cannot access this object because a different thread owns it.
。
这真让我感到困惑,因为窗口是在window.Show
的调用之上创建的一行,并且是本地的。它是如何由不同的线程拥有的?我该如何解决这个问题?
答案 0 :(得分:1)
我已粘贴您在此处显示的代码,但无法重现该问题。 (它有一个问题,但它不会导致你描述的问题。)不幸的是,当我尝试你发布的代码时,我可以多次调用ShowPleaseWait,我没有看到异常。
必须发生两件事之一。可能,您已经修改了代码,简化了原始示例,以便在此处发布相当小的内容,因此您也可能已经删除了问题。 (如果我不得不冒险猜测,那就是你的Dim窗口就像New windowPleaseWait原来是一个类成员而不是本地成员,所以你最终每次都使用相同的窗口对象。这是唯一的事情我想到这可以解释你描述的症状。)
或者,我在与您不同的上下文中使用代码。
为了调查最后一个,我将解释我在尝试你的代码时所做的事情,这样你就可以看到是否有任何明显不同于你运行它的上下文。我创建了一个新的WPF应用程序,我将您的方法粘贴到MainWindow.xaml.vb代码隐藏中。然后我在窗口中添加了一个按钮,在单击处理程序中,我调用了您的方法:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button2.Click
ShowPleaseWait().Join()
End Sub
我在返回的线程上调用Join有两个原因。首先,我想阻止主UI线程,以验证辅助窗口是否真的在一个单独的线程上运行(它是)。其次,我想验证线程是否真的关闭了 - 用线程中止来关闭它是一种非常不正统的做法。 (正确的做法是关闭你在该线程上隐式创建的调度程序,这将调用Dispatcher.Run()以便干净地退出。这是我在开始时提到的问题。)
为了让我的示例与你的示例相似,我正在通过调用Thread.Abort来关闭子窗口,即使这不是一个好主意 - 我正在点击处理程序中为该子项上的按钮执行此操作窗口。 (在我看来,问题可能只发生在你从主线程调用该子线程上的Abort时。所以我还添加了几个按钮让我这样做。没有变化 - 我仍然可以显示窗口和然后根据我的喜好多次销毁线程,而不会看到你描述的错误。)
因此,您使用此代码的上下文是导致其失败的原因,或者您在此处发布的代码意外删除了问题。无论哪种方式,恐怕都需要更多的信息。
答案 1 :(得分:1)
我甚至没有在你的代码中发现任何问题。 将这些方法放在主模块中。
Public Function ShowPleaseWait() As System.Threading.Thread
Dim PleaseWait As New System.Threading.Thread(AddressOf LoadPleaseWait)
PleaseWait.SetApartmentState(System.Threading.ApartmentState.STA)
PleaseWait.IsBackground = True
PleaseWait.Start()
Return PleaseWait
End Function
Public Sub LoadPleaseWait()
Dim window As New windowPleaseWait
Try
window.Show()
System.Windows.Threading.Dispatcher.Run()
Catch e As System.Threading.ThreadAbortException
window.Close()
window = Nothing
End Try
End Sub
现在可以在任何需要的地方调用该函数来显示如下窗口。
Dim myThreadWindow as System.Threading.Thread
myThreadWindow = ShowPleaseWait()
中止线程。
myThreadWindow.Abort
答案 2 :(得分:0)
UI操作必须在UI线程上完成。这意味着如果您想从后台线程操作UI内容,那么您需要在执行UI操作之前将正在执行的代码/函数封送回UI线程。
我建议你改变你的方法 - 在后台线程上执行你的“单独的复杂”工作,并将代码从管理窗口的代码中移开。使用像MVVM这样的模式或像Prism这样的框架将有助于这种方法。
你的流程应该是这样的:
将主窗口加载为空状态
启动数据加载UI体验(在您的情况下,“请稍候”动画)
启动后台主题
使用该后台线程来获取和/或操作数据
完成后台线程后,填充ViewModel,或者将代码执行重新编组回UI线程以填充UI
再一次,看一下使用MVVM或类似的东西,这将有助于分离出每个工作的位置(即后台线程可以在模型中执行,永远不会靠近视图)。