Could someone shed some light on an issue I'm having?
I'm working on a wpf project. The scenario is as below:
I need to pop up a window(model window) on main UI thread and then close it. These works are started from another UI thread (to deter user from clicking on the main UI window.) then I close this window. The main code are displayed below. And it works.
As far as I know the close method would not get excuted before ShowDialog() returns (at least this is the case on UI thread, I mean code without dispatcher), does anyone have experience with multithread?
Window window;
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(() =>
{
//create a window and let user work from this thread
//code is omitted.
//create another window on main UI thread
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
window = new Window();
window.ShowDialog();
}));
//do some work here
Thread.Sleep(1000);
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
//Thread.Sleep(1000);
window.Close();
}));
});
thread.Start();
}
Thank you for your time!
So if I understand your question correctly, you're saying that this code works exactly the way you want, but you're just trying to understand how (and why) it works?
Here's how it works. First, your thread runs this code:
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
window = new Window();
window.ShowDialog();
}));
That queues your action on the main (UI) thread's dispatcher queue, and then returns immediately: your worker thread continues running.
When the Application first started up (typically via the compiler-generated code that initializes your App.xaml object, though you can also do it explicitly by calling Application.Run), it started its message loop, which goes something like this (pseudocode, very very simplified):
public class Application {
public void Run() {
while (!Exited && action = Dispatcher.DequeueAction())
action();
}
}
So at some point shortly after you queue the action, the UI thread will get around to pulling your action off the queue and running it, at which point your action creates a window and shows it modally.
The modal window now starts its own message loop, which goes something like this (again, very simplified):
public class Window {
public bool? ShowDialog() {
DisableOtherWindowsAndShow();
while (!IsClosed && action = Dispatcher.DequeueAction())
action();
EnableOtherWindowsAndHide();
return DialogResult;
}
}
Later, your worker thread runs this code:
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
window.Close();
}));
Again, your action is queued to the UI thread's dispatcher queue, and then the BeginInvoke call returns immediately and your worker thread continues running.
So sooner or later, the UI thread's message loop will get around to dequeuing and executing your action, which tells the window to close. This has essentially the same effect as the user clicking the title bar's "X" button, which of course is perfectly OK to do even when you're inside a modal dialog. This causes ShowDialog's message loop to terminate (because the window is now closed), at which point the dialog is hidden and the other windows are re-enabled, ShowDialog returns, your original (ShowDialog) action is complete and so returns, and control falls back to the original message loop in Application.Run.
Note that there's one dispatcher queue per thread, not one per message loop. So your "close" action goes into the same queue that your "show dialog" action did. It's a different piece of code doing the message-loop polling now (the one inside ShowDialog instead of the one inside Application.Run), but the basics of the loop are the same.
BeginInvoke is a non-blocking method; it adds the action to the dispatcher queue, and doesn't wait for its completion. You should use Invoke instead, which calls the method synchronously on the dispatcher thread.
Related
Hi I had posted a question along these lines recently but this is now a little more specific to my requirements. So, I have an Application where the user needs to log in. The log in process can take some time so I decided to put up a little animated GIF to show it is doing something. Sounds simple...!!??
I noticed soon that the login process was freezing the animation so I thought, I will put the login process on its own thread. I had countless instances of it referencing objects on the UI Thread so thought I would try the other way round and have the Image display on a new thread. Same issue - so I decided to create a new window containing the image, format it accordingly and display this as a new thread! Simple! That (bit) worked... I click to login, animation appears and disappears onces login is complete. So the Thread variable is set as global one:
Friend g_thLoading As Thread
And when the Login button is clicked I have the following:
g_thLoading = New Thread(AddressOf LoginSplashScreen)
g_thLoading.SetApartmentState(ApartmentState.STA)
g_thLoading.IsBackground = True
g_thLoading.Name = "LoginThread"
g_thLoading.Start()
VerifyLogin() 'Process that takes a while...
g_thLoading.Abort()
Then the method that is called in the new thread:
Sub LoginSplashScreen()
Dim SplashScreenWin As New SplashScreen()
Try
SplashScreenWin.ShowDialog()
System.Windows.Threading.Dispatcher.Run()
Catch ex As Exception
SplashScreenWin.Close()
SplashScreenWin = Nothing
End Try
End Sub
This works - but not if I have to click the button more than once. However If (for example) the user enters the wrong credentials, clicks login (the above processes and completes) they are prompted to re-enter - click the login button again... but this time, the window doesnt display (but oddly does appear in the Task Bar)... Then the application is forced to close (nothing in debug on why that is).
I am confident that the Dialogue Window is closing correctly after the first instance as i) it is no longer in the Task Bar and secondly I have put some checks on the Windows Close event. I am fairly confident that the created Thread is closed after the first instance as I can see it drop off from the Thread Window in Visual Studio... So - I am at a total loss. I have also tried the Join function on the thread but this just hangs the process before it gets to g_thLoading.Abort()
I am open to any advice on how I can go about achieving my end goal... whether it is expanding on what I have done here or another suggestion altogether. I have messed around with the Background Worker but not had much more luck there.
Use the BackgroundWorker class to implement your long running processes. The class allows you to specify code that will run on a background thread (in the DoWork event handler), code that will run during "updates" on the thread that created the BackgroundWorker in the ProgressChanged event handler, and code that will run when the process completes, again on the thread that created the BackgroundWorker in the RunWorkerCompleted event handler.
Using it goes something like this:
private class LoginParameters {
public string Name { get; set; }
public string Password { get; set; }
// Any other properties needed
}
// Make this a property of your form.
BackgroundWorker LoginWorker { get; set; }
// Somewhere in your UI code after the user clicks the "login button"
LoginWorker = new BackgroundWorker();
LoginWorker.WorkerReportsProgress = true;
LoginWorker.WorkerSupportsCancellation = true; // Can set to false if you don't allow the operation to be cancelled.
LoginWorker.DoWorker += DoLogin;
LoginWorker.ProgressChanged += ReportProgress;
LoginWorker.RunWorkerCompleted += LoginFinished;
LoginParameters login = new LoginParameters {
// Code to initialize everything here
};
LoginWorker.RunWorkerAsync(login);
// Put this in the click event handler for the Cancel button, if you have one
if ( LoginWorker != null )
LoginWorker.CancelAsync();
private void DoLogin(object sender, DoWorkEventArgs e) {
BackgroundWorker worker = (BackgroundWorker) sender;
LoginParameters login = (LoginParameters) e.Argument;
// Your logic to process the login goes here. It should periodically do the following to check to see if the user clicked the cancel button:
if ( worker.CancellationPending ) {
e.Cancel = true;
return;
}
// When you want to update the UI, do this:
worker.ReportProgress( percentComplete, objectWithOtherDataToWriteToTheUI );
// When you're done, just return.
}
private void ReportProgress(object sender, ProgressChangedEventArgs e) {
// Your code to extract the data you need to update the display from the arguments & to then update the display goes here. Remember, this runs on the UI thread
}
private void LoginFinished( object sender, RunWorkerCompletedEventArgs e ) {
if (e.Cancelled == true)
// Your code to inform the user of the cancellation here
else if (e.Error != null)
// All unhadgled exceptions throws by the DoWork event handler end up here
// Your code to inform the user of the error here
else {
// Your code to inform the user of the success goes here.
// Remember, this runs on the UI thread.
// I recommend you set the form BackgroundWorker property to null after its finished, as you can't reuse it after its finished.
LoginWorker = null;
}
}
Sorry this is in C# if you're looking for VB.NET, but it shouldn't be hard to translate.
As part of my App's startup procedure, it checks data integrity, and if it finds a problem it pops up a message to the user telling them that it might take a while to repair things.
I'm showing the message using MessageBox.Show. Because the data check is done from a worker thread, I'm switching over to the UI thread to make that call, and then setting a ManualResetEvent to tell the worker thread when the user has acknowledged the message.
I kick off the data check/load very early in the app's lifecycle from the constructor in the main Application class, by spinning off a worker thread (using the ThreadPool).
When I run with the debugger, and the message is displayed, the app just waits for input. When I run without the debugger, the app terminates after displaying the dialog for 10 seconds.
That 10 seconds is a big clue - it tells me that the OS thinks the app took too long to initialize (the OS kills apps that take too long to start up).
I think that my MessageBox.Show is blocking the UI thread before the App.RootFrameNavigating has a chance to be invoked.
My questions:
Does my diagnosis sound right?
I'd prefer to kick off my data load early, because it is almost entirely IO, except for this Message Box, and the sooner I can get my Model loaded, the better, but do you normally delay your data load until later in the app lifecycle?
Any other ideas/suggestions? I can't guarantee which page will be the start page, because the app could be resuming to any page. I'm also thinking of having the MessageBox.Show delay itself until the app has initialized, perhaps polling away for a flag set by App.RootFrameNavigating - does that make sense?
I think your problem is a result of kicking off the worker thread in the Application constructor. You should use the appropriate life-cycle event, in this case: PhoneApplicationService.Activated Event
So, the solution I've come up with is to still kick off the data load in a worker-thread from the Application's constructor, but in my PhoneService's class ShowDialog method that I invoke to invoke MessageBox.Show, I check to see if the initial navigation has occurred:
private readonly ManualResetEvent _appInitialized = new ManualResetEvent(false);
public void AppInitialized()
{
_appInitialized.Set();
}
public void ShowDialog(string caption, string text, Action<MessageBoxResult> callback, MessageBoxButton button = MessageBoxButton.OKCancel)
{
_appInitialized.WaitOne();
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
var result = MessageBox.Show(text, caption, button);
if (callback != null)
{
callback(result);
}
});
}
Then in my Application class:
private bool _firstNavigate = true;
private void RootFrameNavigating(object sender, NavigatingCancelEventArgs e)
{
if (_firstNavigate)
{
_firstNavigate = false;
var navigationService = (NavigationService) sender;
navigationService.Navigated += NavigationServiceNavigated;
}
....
private void NavigationServiceNavigated(object sender, NavigationEventArgs e)
{
var navigationService = (NavigationService)sender;
navigationService.Navigated -= NavigationServiceNavigated;
PhoneServices.Current.AppInitialized();
}
Anyone see any issues with this approach? Anyone come up with a better way?
I have a class that handles some realtime action in a thread that it starts. There are other theads in play in this application as it is very complex. When This rt action starts i need to pop up a window and close it when it is done. Sounds easy.
There are events that I hook to when this action starts and stops. In those event handlers I place the code:
private void Voice_SpeakStarted(object sender, TMSpeakStartedEventArgs e)
{
InfoWindow = new Form();
InfoWindow.Show();
}
/// <summary>
/// this is the event handler speaking stops
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Voice_SpeakCompleted(object sender, TMSpeakCompletedEventArgs e)
{
if (InfoWindow.InvokeRequired)
{
InfoWindow.Invoke(new Action(() =>
{
InfoWindow.Close();
}));
InfoWindow.Hide();
}
}
There are times I get an error that the thread has terminated. (An error occurred invoking the method. The destination thread no longer exists.)
I always seem to get the window to show. I can not seem to get the window to close.
I also see that sometime the handler themselves do not get called.
I need help bad. I can post more code if it would be helpful.
Edited - added more code
This is how I start the class
public void start()
{
//It's already alive, nothing to do
if (alive) return;
//Make sure this is the only place where alive is set to true
alive = true;
Voice.SpeakCompleted += new Speech.TMSpeakCompletedDelegate(Voice_SpeakCompleted);
Voice.SpeakStarted += new Speech.TMSpeakStartedDelegate(Voice_SpeakStarted);
dispatch = new Thread(new ThreadStart(ProcessSayList));
dispatch.Start();
}
The constructor of the class is
public AnimationControl(dynamic parent)
{
Parent = parent;
Voice = new Speech();
q = Queue.Synchronized(new Queue(1000));
start();
}
You should really specialize your thread and stop calling code managed by other thread form any thread. Use message queues to communicate actions to your thread. This is the safest way to do multi-threading.
Example in pseudo-code:
Thread1
{
while (1)
{
read my last message in my queue;
do something according to this message like:
openwindow();
or closewindow();
}
}
Thread2
{
My life is short, I just need to post a message to thread1
}
Thread3
{
etc.
}
There are ready-to-use structures for such a thing on every system. And by doing so, it will be much more easy to understand what's happening in case of problem like that. Of course, if you don't take care, your threaded program can become absolutely linear; the goal is to make sure some parts of actions can be made in parallel and do not create a chain of threads waiting each other, one by one :)
Check my answer here for some information on the thread terminated error, and UI threading in general: understanding InvalidAsynchronousStateException occurrences
Well for one thing you're calling Hide on your window on the wrong thread even if InvokeRequired returns true.
I can also guess that you're creating your Voice instance on a new thread and returning when you get a "close" message, thus sometimes killing the thread you're trying to route windows messages through. Consider holding on to the thread until your window is actually closed.
And yes, you should post more code, there isn't a single line of code that shows how you create/dispose of that thread.
I try to delay close a window in my App.xaml.ca :
Window splash = new Window();
splash.Show();
Timer timer = new Timer(callback, null, 2000, Timeout.Infinite);
private void callback(object stateInfo)
{
splash.Close();
}
It works fine, but the whole App is shutdowning. What am doing wrong here ?
Be sure to check that you timer callback is coming back on the main dispatcher thread. If not then you will likely be getting an exception when you try to close your window from a different thread.
Use splash.Dispatcher.CheckAccess() to make sure you are on the right thread and if not then use splash.Dispatcher.BeginInvoke((Action) () => splash.Close() to dispatch the call onto the main thread.
Check out this page for more
Here is my solution to this exact same problem:
private async void CloseWindow()
{
await ClosingTasks();
}
private async Task ClosingTasks()
{
await Task.Delay(2000);
this.Close();
}
Where you simply call CloseWindow() when you want to close the current window after the given delay of 2000 mS.
There are different shutdown modes, if that window is closed and it is the last then the application will shut down by default. So you can either see to it that there is still some window around or you can change the shutdown behaviour by setting the ShutdownMode to something that suits your needs.
e.g.
Application.Current.ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;
What is the best way to update a label on a Windows Forms application while processing?
I have a loop that does some processing to files on the user's system when the user clicks a button.
foreach (System.IO.FileInfo f in dir.GetFiles("*.txt"))
{
// Do processing
// Show progress bar
// Update Label on Form, "f.Name is done processing, now processing..."
}
What would be some sample code?
What exactly is this called? Is it threading or delegates?
A quick fix for you would be:
Label1.Text = f.Name + " is done processing, now processing...";
Label1.Refresh();
You really want to avoid DoEvents, otherwise you'll have problems if your user repeatedly presses buttons on your form.
You should be doing this on another thread, and then updating your UI thread from that thread. You are blocking further processing by performing this work on the UI thread.
If you can't move this code to the UI thread, then you could always call Application.DoEvents, but I strongly suggest you explore these options first:
System.ComponentModel.BackgroundWorker
System.Threading.ThreadPool
System.Threading.Thread
System.Threading.Tasks namespace
You'll need to get your data from one thread to the other. This can be done in a couple of ways...
First, your "background" thread could update some kind of "CurrentStatus" string variable that it changes as it goes along. You could then put a timer on your form that would then grab the CurrentStatus variable and update the label with it.
Second, you could simply invoke the operation from the background thread to the UI thread with a delegate using the InvokeRequired property of the label control. So for example...
private delegate void UpdateStatusDelegate(string status);
private void UpdateStatus(string status)
{
if (this.label1.InvokeRequired)
{
this.Invoke(new UpdateStatusDelegate(this.UpdateStatus), new object[] { status });
return;
}
this.label1.Text = status;
}
You can call that UpdateStatus() method from any thread (UI or background), and it will detect whether or not it needs to invoke the operation on the main UI thread (and if so, does it).
To actually set up the thread, you can do so like this:
private void StartProcessing()
{
System.Threading.Thread procThread = new System.Threading.Thread(this.Process);
procThread.Start();
}
private void Process() // This is the actual method of the thread
{
foreach (System.IO.FileInfo f in dir.GetFiles("*.txt"))
{
// Do processing
// Show progress bar
// Update Label on Form, "f.Name is done processing, now processing..."
UpdateStatus("Processing " + f.Name + "...");
}
}
Then when the user clicks the "GO" button you'll simply call StartProcessing().
If your processing is lengthy do it in a backgroundworker thread.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
I also recommend to use :
Application.DoEvents();
Processes all Windows messages currently in the message queue.