Click here to Skip to main content
15,885,767 members
Please Sign up or sign in to vote.
5.00/5 (3 votes)
Hi everyone,

I've written an application in C# that uses WPF (utilising the MVVM design pattern) and LINQ to CRM to retrieve data from a database.

I have a problem with threading it seems...

This application uses the BackgroundWorker to run a database query. Once it retrieves this information it manipulates it in memory and creates a list of 'Structs'. Whilst it is processing this data I update the UI at regular intervals and show a loading animation.

So far everything works fine.

When this application is deployed however, there is a possibility that this operation could fail and in this case I want to capture this error and let my program fail gracefully. I handle this possibility by encapsulating the data processing in a 'try-catch' block. If an error occurs such as a database time out, I clear all temporary variables, including the data sources that my UI controls are bound to, hide my loading animation and return to the initial screen. The program is then in the state it should be when first initialised.

To alert the user to this event I show a MessageBox which tells them what has happened.

Here lies the problem..

This MessageBox is not modal, it still allows the user to interact with the main window. This could cause an issue, as a user could try to restart this operation before they have selected OK, to acknowledge the error, clearing the temporary variables and resetting the loading animation.

This might seem trivial but as this is an end-user application it needs to be polished.

What I think the issue is...

The MessageBox is being created on a thread other then the STA thread (which I understand to be the main thread of execution, correct me if I'm wrong) therefore, you are able to minimise it and still interact with the other window as it is not blocking the execution of the main thread.

Things I have already tried...

  • The try catch block is inside the background.DoWork += delegate {...} block, so instead of creating the message box inside the catch block of that delegate, I throw a custom event, naively thinking that outside the scope of that delegate it would return to the STA thread and create a thread blocking modal. No dice.

  • In this event handler I create a new thread and set it's apartment state to STA.
    myThread.SetApartmentState(ApartmentState.STA); This was a bad idea and didn't work but it was a very long shot.

    Here is the basic structure of my code :
    BackgroundWorker background = new BackgroundWorker();
    								background.RunWorkerCompleted += new 
    
    WorkerFailed += new workerFailedEvent(GetDD_Command_WorkerFailed);	
    			
    			background.DoWork += delegate
    			{
    			try
    			{
    			 // Do very long operation
    			}
    			catch (Exception e)
    			{	
    				// Something went wrong, call event						
    				WorkerFailed(e, new EventArgs()); 
    			}	
    		};
    		background.RunWorkerAsync();	
    	}
    
    	void myProgram_Command_WorkerFailed(Exception ex, EventArgs e)
    	{						
    		Exception exception = (Exception)ex; 
    		// Creates Message box
    		System.Windows.Forms.MessageBox.Show
    			(
    			exception.InnerException == null ? exception.Message.ToString() : exception.InnerException.ToString(),
    			"Error",
    			MessageBoxButtons.OK,
    			MessageBoxIcon.Error
    			);
    			// Also clears cache and clears temp variables and hides loading animation
    		}
  • Thanks for any help or advice anyone gives! :)

    Larry
    Posted
    Updated 9-May-11 0:21am
    v5

    To display your message box from the UI thread, just use BeginInvoke:

    C#
    yourMainForm.BeginInvoke((Action)(() =>
    {
        //all the code here will be executed in the main thread
        MessageBox.Show("...");
    }));


    Where yourMainForm is a reference to your main form. Actually it can be a reference to any form/control created on the main thread.

    BeginInvoke will make an asynchronous call (the function will not wait that the call is finished and will return without blocking the calling thread). If you need a synchronous call, use Invoke (but be careful to deadlock issues).
     
    Share this answer
     
    v2
    Comments
    Laurence1234 9-May-11 8:45am    
    Hi Olivier,

    Thanks for your answer.

    Unfortunately it doesn't look like I have access to BeginInvoke()

    The MainWindow.xaml.cs partial class doesn't seem to expose this method so I have no way of threading it through into my ViewModel.

    The reason may be that this is a WPF project. If I create a System.Windows.Forms.TextBox as an example, this allows me to call BeginInvoke() however, it doesn't look like the controls that I'm using or the Code Behind/partial class (whatever you like to call it) allow me to do this.

    My 5 though, I'm willing to be proved wrong on this one.

    Cheers

    Laurence
    Olivier Levrey 9-May-11 8:58am    
    Did you check this thread?
    http://stackoverflow.com/questions/758233/winforms-to-wpf-conversion-begininvoke-to-what
    I think you just need to put Dispatcher somewhere.
    Thank you for the vote.
    Laurence1234 9-May-11 11:45am    
    Hi,

    Thanks for the link, in the end I tried:

    Dispatcher.CurrentDispatcher.BeginInvoke((Action)(() =>
    {
    //all the code here will be executed in the main thread
    MessageBox.Show
    (
    ex.InnerException == null ? ex.Message.ToString() : ex.InnerException.ToString(),
    "Error",
    MessageBoxButton.OK, MessageBoxImage.Error

    );

    }));

    However this didn't seem to work either ...

    Laurence
    Olivier Levrey 9-May-11 12:20pm    
    No you shouldn't use CurrentDispatcher, you must use the dispatcher from your main form (or any form created from the main thread).
    Sergey Alexandrovich Kryukov 9-May-11 12:34pm    
    Olivier, I think this is wrong approach. Throwing and catching exception in one thread and using in another one is the root of the problem. Tricks with Dispatcher and whole MessageBox idea may be not enough. For clear solution it needs synchronization of threads. The problem is that the thread should be paused at the time MessageBox is running and wait for the user's decision. (OP is wrong, it ***is*** truly modal, but threads do not care about stupid modal behavior :-)
    --SA
    Please see my comments to the solution by Olivier.

    Here is one resolution: have a shared instance of System.Threading.EvenWaitHandle and make is reset before handling exception. In the thread handling exception, use Dispatcher.BeginInvoke and immediately call EvenWaitHandle.Wait. This thread will go to the wait state by OS and will not spend any CPU time until awaken. The invoked code will run in the UI thread. After MessageBox has finished its modal state (it is truly modal, by the way, see my comment) call EvenWaitHandle.Set on the same instance. It will wake up the waiting thread. Need to pass some data from the MessageBox to the thread? Add some shared data, protect the access to it with lock.

    Be careful with all that.

    —SA
     
    Share this answer
     
    Comments
    Olivier Levrey 9-May-11 12:53pm    
    Sorry but I don't see the point.
    Your solution relies on BeginInvoke as well and you are just adding an extra layer that makes error handling harder than needed. If OP wanted to handle the error and continue using the application it would make sense, but he said he just wants to reset it to its original state, so why would you make things more complicated than they are?
    Sergey Alexandrovich Kryukov 9-May-11 20:35pm    
    Not sure... What happens to the thread when the dialog box is in modal state? If it just finishes, you're right...
    --SA
    Sergey Alexandrovich Kryukov 10-May-11 13:47pm    
    Olivier, so I already agree that your solution will work as the thread will terminate anyway. My solution is simple more general. Please also see my comments in discussion with Bob.
    --SA
    Laurence1234 9-May-11 17:14pm    
    Hi,

    Thanks for your input. I have found a solution which I will post later.

    Laurence
    The DispatcherSynchronizationContext Class[^] provides a synchronization context for Windows Presentation Foundation (WPF).

    You'll find an example here: Task Parallel Library: 1 of n[^]

    Regards
    Espen Harlinn
     
    Share this answer
     
    Comments
    Laurence1234 9-May-11 17:14pm    
    Hi,

    Thanks for your input. I have found a solution which I will post later.

    That second link by the way is really good, I might use that solution in the future.

    Laurence
    Sergey Alexandrovich Kryukov 9-May-11 20:38pm    
    Espen, could you explain how this can be used in this case? OP concern was executing MessageBox.Show when exception was handled in other thread.
    --SA
    Espen Harlinn 10-May-11 2:59am    
    use DispatcherSynchronizationContext.Post to execute the argument delegate on the WPF ui thread
    Sergey Alexandrovich Kryukov 10-May-11 13:17pm    
    Why just WPF? Dispatcher.Invoke or BeginInvoke does work on Forms as well. Maybe DispatcherSynchronizationContext.Post too?
    --SA
    Espen Harlinn 11-May-11 12:54pm    
    MS created the WindowsFormsSynchronizationContext for use with Windows Forms applications
    Hi everyone,

    I have a solution to my problem.

    Thanks for your suggestions.

    After attempting to implement a lot of different solutions, I think I've found one which is a lot simpler then my original ideas and appears to solve the problem with the least amount of additional code.

    // exception
    public Exception handledException { get; set; } 
    ...
    public void Execute(object parameter)
    {
    	// Show my loading animation
    	// Clear temporary variables collectstions etc
    	BackgroundWorker background = new BackgroundWorker();
    							
    	background.RunWorkerCompleted += new RunWorkerCompletedEventHandler(background_RunWorkerCompleted);
    			
    	background.DoWork += delegate
    	{				 
    		try
    		{
    			// Do long operation here
    		}
    		catch(Exception ex)
    		{					
    			ViewModel.handledException = ex; 
    		}
    	};
    		
    	background.RunWorkerAsync();
    }
    							
    void background_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
    	if (ViewModel.handledException != null)
    	{
    		MessageBox.Show
    			   (
    			   ViewModel.handledException.Message.ToString(),
    			   "Error",
    			   MessageBoxButton.OK, 
    			   MessageBoxImage.Error
    			   );
    		
    		// hide my loading animation
    		// clear all variables
    		// clear XRM chache
    	}
    	else
    	{
    		// group and order collections
    		// set properties that controls are bound to to equal collections
    	}
    }


    So with this approach, I have an exception property of my ViewModel class(don't worry I set that variable to null when I call my ClearAll() function at the end of each operation).

    If an exception occurs this variable is set. The program exits the try-catch and reaches the end of the DoWork delegate. Therefore the RunWorkerCompleted event is raised. Once this happens all background operations are completed. I did try and manually use the cancel async operation, but I couldn't get that to work.

    Once inside that event handler I check to see if my exception property is null. If it isn't an exception occurred which I alert the user to and reset the application to it's initial state, if not I set the observable properties that the controls are bound onto, to the values of my collections.

    When the message box appears, you can't interact with the window underneath until it's been closed, by which time all of the variable have been cleared and the application is in a stable state.

    Thanks for your help everyone. Any feedback on this approach?

    Laurence
     
    Share this answer
     
    v2
    Comments
    Laurence1234 10-May-11 7:28am    
    Ok - Update on the issue.

    It looks like this has only partially solved the problem. Now I have a really weird situation.

    When the application is in focus if there is an error, the error box appears and stops you interacting with the main window until it's closed. That is fine.

    However, if the application is minimised or not in focus, the error box is created as a separate window on the task bar. And, once again you can interact with the main window before clicking 'OK' to acknowledge the error.

    This is a very confusing problem.

    Laurence
    Olivier Levrey 10-May-11 8:21am    
    Laurence you don't need to add ViewModel.handledException adn you don't need the try/catch block because a BackgroundWorker already implements this.

    Have a look to RunWorkerCompletedEventArgs argument in the RunWorkerCompleted function: you will find the Error property. This property is the exception thrown by the DoWork function if there was one.
    Laurence1234 10-May-11 9:24am    
    Hi Oliver,

    I did think that and tried it out, but when I took out the try/catch the program failed completely and stepped into the exception as the exception wasn't handled.

    Thanks for your help

    Laurence
    BobJanova 10-May-11 10:05am    
    It will probably work correctly outside the debugger ... VS is overly aggressive about exception breakpoints sometimes.
    Olivier Levrey 10-May-11 10:06am    
    Try it without the debugger and you will see it works (I do it often and many other people do). If you use the debugger, it will catch the exception before you.
    Use dispatcher when you want to update your UI thread from bakground Thread.
    for e.g. in your case
    C#
    _myView.Dispatcher.Invoke(DispatcherPriority.Background, new Action(() =>
                {
                           MessageBox.Show("Some error occurred while processing data ");
                }));
     
    Share this answer
     
    Comments
    CHill60 25-Jun-15 6:52am    
    This question is over 4 years old and already resolved. You have added nothing to the discussion as use of Dispatcher is already mentioned in several of the solutions and comments posted 4 years ago!

    This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



    CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900