Click here to Skip to main content
Rate this: bad
good
Please Sign up or sign in to vote.
See more: C# MVVM multithreading
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! Smile | :)
     
    Larry
    Posted 9-May-11 0:10am
    Edited 9-May-11 0:21am
    v5
    Rate this: bad
    good
    Please Sign up or sign in to vote.

    Solution 1

    To display your message box from the UI thread, just use BeginInvoke:
     
    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).
      Permalink  
    v2
    Comments
    Laurence1234 at 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 at 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 at 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 at 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).
    SAKryukov at 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
    Olivier Levrey at 9-May-11 12:39pm
       
    Well Laurence said the application will just return to its initial state after the exception is thrown, so I don't think it is necessary to setup a complicated error handler.
    SAKryukov at 9-May-11 12:41pm
       
    I got some solution. Please see.
    --SA
    BobJanova at 10-May-11 10:03am
       
    In general you are correct but the exception causes the initiating thread to terminate anyway, so for this specific type of case (which is actually quite common) it is fine.
    SAKryukov at 10-May-11 13:44pm
       
    I already got it, thank you for the note. Well, just consider my solution to be applicable to a more general case, should anyone face it.
     
    By the way, I personally would not make such design in principle. In my design the whole idea of showing any kind of modal dialog in response to any thread condition would never be used.
    --SA
    SAKryukov at 10-May-11 13:45pm
       
    In view of arguments of Olivier and Bob ("the initiating thread to terminate anyway") I up-voted this solution by my 5.
    --SA
    Olivier Levrey at 11-May-11 3:48am
       
    Thank you for understanding me SA.
    SAKryukov at 11-May-11 15:30pm
       
    Anytime. (Almost :-)
    --SA
    Rate this: bad
    good
    Please Sign up or sign in to vote.

    Solution 2

    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
      Permalink  
    Comments
    Olivier Levrey at 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?
    SAKryukov at 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
    SAKryukov at 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 at 9-May-11 17:14pm
       
    Hi,
     
    Thanks for your input. I have found a solution which I will post later.
     
    Laurence
    Rate this: bad
    good
    Please Sign up or sign in to vote.

    Solution 3

    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
      Permalink  
    Comments
    Laurence1234 at 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
    SAKryukov at 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 at 10-May-11 2:59am
       
    use DispatcherSynchronizationContext.Post to execute the argument delegate on the WPF ui thread
    SAKryukov at 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 at 11-May-11 12:54pm
       
    MS created the WindowsFormsSynchronizationContext for use with Windows Forms applications
    SAKryukov at 11-May-11 16:09pm
       
    So it's for Forms and WPF as well, right?
    --SA
    Rate this: bad
    good
    Please Sign up or sign in to vote.

    Solution 4

    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
      Permalink  
    v2
    Comments
    Laurence1234 at 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 at 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 at 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 at 10-May-11 10:05am
       
    It will probably work correctly outside the debugger ... VS is overly aggressive about exception breakpoints sometimes.
    Olivier Levrey at 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.
    Laurence1234 at 10-May-11 11:35am
       
    Ok thanks, I'll give it a go.
     
    Do you mean running it in Release mode?
     
    Also I still have the problem that if it looses focus then when the error is thrown (no matter how we get it to do that) the text box doesn't stop you interacting with the main window. But if you keep it open it behaves correctly.
     
    Cheers
     
    Laurence
    BobJanova at 10-May-11 12:32pm
       
    No, just double click the Exe, don't run it from within VS at all. You can, I believe, set an option in VS to stop it being so aggressive with exceptions.
    Laurence1234 at 11-May-11 5:06am
       
    Ah ok, I will give that a go, thanks.
     
    I still have this weird issue though that depends on the main window being minimised.
     
    Laurence
    Laurence1234 at 10-May-11 11:38am
       
    Hmmmm, even in release mode the debugger will kick in and show the exception it seems ...

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

      Print Answers RSS
    0 Sergey Alexandrovich Kryukov 379
    1 Nirav Prabtani 266
    2 Abhinav S 210
    3 PIEBALDconsult 160
    4 Dave Kreskowiak 155
    0 OriginalGriff 7,545
    1 Sergey Alexandrovich Kryukov 6,757
    2 Maciej Los 3,909
    3 Peter Leow 3,693
    4 CHill60 2,712


    Advertise | Privacy | Mobile
    Web03 | 2.8.140721.1 | Last Updated 10 May 2011
    Copyright © CodeProject, 1999-2014
    All Rights Reserved. Terms of Service
    Layout: fixed | fluid

    CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100