Reputation: 603
I am trying to give some user entertainment, and show a "please wait" window, with Marquee, during the loading of a separate complex Window. I am attempting to do this by loading the Window in a new thread, like this:
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
In the calling code, it calls ShowPleaseWait
and saves the Thread for later.. To close the window, it calls Thread.Abort
, on the saved thread. This in turn will causes it to enter the Catch
. I have tried, many different ways, with and without the catch.
This works incredibly, the first time it is called. However, additional calls will fail at window.Show()
with the exception: The calling thread cannot access this object because a different thread owns it.
.
This really puzzles me as the window was created one line above the call to window.Show
and is local. How is it owned by a different thread? How can I fix this?
Upvotes: 1
Views: 3539
Reputation: 13
I did not even find any problem in your code. Put these methods in a main module.
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
Now call the function wherever you need to show the window as below.
Dim myThreadWindow as System.Threading.Thread
myThreadWindow = ShowPleaseWait()
To abort the thread.
myThreadWindow.Abort
Upvotes: 1
Reputation: 14547
I've pasted the code you've shown here, and cannot reproduce the issue. (It has a problem, but it's not causing the issue you describe.) Unfortunately, when I try the code you've posted, I can call ShowPleaseWait as many times as I like, and I don't see the exception.
One of two things must have happened. Possibly, you've modified the code, simplifying the original example in order to have something reasonably small to post here, and as a result, you may also have removed the problem. (If I had to hazard a guess, it'd be that your Dim window as New windowPleaseWait was originally a class member rather than a local, and so you're ending up using the same window object each time round. That's the only thing I've thought of that would explain the symptoms you describe.)
Alternatively, I'm using the code in a different context than you.
To investigate last one, I'll explain what I'm doing to try out your code, so you can see if there's anything obviously different from the context you're running it in. I created a new WPF app, and I pasted your methods into the MainWindow.xaml.vb codebehind. I then added a button to the window, and in the click handler, I called your method thus:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button2.Click
ShowPleaseWait().Join()
End Sub
I call Join on the returned thread for two reasons. First, I wanted to block the main UI thread, to verify that the secondary window really was running on a separate thread (which it was). Second, I wanted to verify that the thread really was shutting down - shutting it down with a thread abort is a very unorthodox way to do things. (The correct thing would be to shut down the dispatcher you've implicitly created on that thread, which would enable the call to Dispatcher.Run() to exit cleanly. This is the problem that I mentioned at the start.)
To try and keep my example similar to yours, I'm shutting down the child window by calling Thread.Abort, even though it's not a good idea - I'm doing that in a click handler for a button on that child window. (It occurred to me that the problem might only occur when you call Abort on that child thread from the main thread. So I also added a couple of buttons to let me do it that way. No change - I can still show the window and then destroy the thread as many times as I like without ever seeing the error you describe.)
So either the context in which you're using this code is what makes it fail, or the code you've posted here has accidentally had the problem removed. Either way, more information is required I'm afraid.
Upvotes: 1
Reputation: 49984
UI manipulation has to be done on the UI thread. This means that if you want to manipulate UI stuff from a background thread then you need to marshal the code/function being executed back onto the UI thread before doing the UI actions.
I would suggest you change your approach - do your "separate complex" grunt work on the background thread, and move that code away from the code that manages the window. Using patterns like MVVM or frameworks like Prism will help with this approach.
Your flow should be something like this:
load your main window in an empty state
kick off your data load UI experience (in your case, the "please wait" animation)
start a background thread
use that background thread to fetch and/or manipulate data
once the background thread is done, populate your ViewModel, or marshal the code execution back on to the UI thread to populate the UI
Once again, look at using MVVM or similar, this will help separate out where each of these bits of work are done (i.e. the background thread can execute in the Model and never come near the View).
Upvotes: 0