A little bit of background first:
1. I am developing a WPF application that has many long running processes that need to be run on a background thread whilst at the same time providing feedback and a means of cancelling to the user.
2. I am using an MVC approach where the Model (a CSLA BO graph) is bound to the view.
3. I am on CSLA 3.8 but I am planning to upgrade to 4.0 when it is released.
When I am processing in the background thread I need to set values and add items to the CSLA object graph but this raises PropertyChanged events and ends up crashing the application because it tries to update the UI from that thread.
When this happens with lists I set the RaiseListChangedEvents to false before adding items and reset it when I am done. Since there isn't a corresponding property for the other CSLA objects I had to add my own and act on it by overriding OnPropertyChanged.
My question is, is there a better way to do this? If not could I suggest the addition of a RaiseListChangedEvents to the base classes?
Unfortunately the problem goes much deeper than just that event. Like WPF, CSLA is not threadsafe. It is not valid to have more than one thread interact with a business object at the same time. You are seeing one symptom, but even if we fix this one, there are much worse ones - like business rules running on different threads and generating race conditions, etc.
In short, you need to figure out a way by which the object is not being used by code on 2+ threads at the same time. This means the object can't be bound to the UI while it is modified by a background thread, or the background thread can't directly modify the object.
The right answer depends a lot on your app, and what you are doing on that background thread.
I often use background threads to retrieve extra data or do server interactions. In those cases, I have the background thread do whatever it is going to do, and it then returns a message to the UI thread, and a handler takes the message data and uses it to update the business object. That way the business object graph is only used by the UI thread, so I can keep the graph bound to the UI. This is pretty easy to do with the BackgroundWorker component, since the messages are automatically provided to the UI thread as the background task performs explicit progress notifications or completes.
Thanks for the reply. I have used message passing in similar systems but this application is more about doing long running calculations at the client (with an option for cancelling) and displaying the results on screen with less emphasis on CRUD. I am indeed using the BackgroundWorker and I was hoping that this time I could avoid the duplication of state between the BOs and the message objects which caused a few bugs the last time around.
From my perspective the ideal situation would be a switch in the CSLA that I could push to stop all UI operations but I understand that this is easier said than done and probably opens a few other cans of worms. I see this as a very important problem for the CSLA because most desktop applications still run exclusively on the UI thread but this is changing. I will investigate further and will report back if I find a reasonable solution.
Yes "disabling all UI operations" is much easier said than done. Consider that really doing this would mean that the object would have to refuse to accept changes from the UI - not honoring property set, property get, IEditableObject, IDataErrorInfo, INotifyPropertyChanged, INotifyCollectionChanged and several other interfaces and operations.
The end result would be that the UI would think it was bound to an object, but in reality the object would be completely inert - ignoring all UI activity, and not updating the UI. It would be useless, but it would be "safe".
I don't think this would get anyone what they want. In your case you want to update the object on the background thread, but I assume you actually want the UI to show the result. Therefore we can't just disable all property get and change notification and business rule notification. We have to have all those things run on the UI thread as a result of the change made on the background thread.
That might be possible if I rebuilt BusinessBase completely from scratch, and made certain assumptions about always, always, always running in Silverlight or WPF, because then I could count on using the Dispatcher to manage all property change operations. There'd be a non-trivial perf hit, as every property get/set would require a context switch, and the objects would never work in ASP.NET, behind a service interface or in a workflow.
So that approach has never seemed attractive to me
Another approach is to create an "object mirror" - sort of like an in-proc data portal, but much finer grained. The idea is that when you spin up a background thread, you'd give it a Clone() of the original object graph. The "object mirror" would listen for events on the original graph and the clone, and would marshal changes bi-directionally across the thread boundary to keep the two object graphs in sync with each other.
I don't honestly know if that'd work. It would certainly be extremely difficult, and I suspect in many cases it would cause major performance issues. To get good performance when marshalling across a boundary like that, you typically want to send course-grained messages, which means you need to understand the nature of the changes and the context - which this "object mirror" couldn't do...
Ultimately, my view is that "background tasks" should be treated and implemented like a service. And that means using a message-based communication metaphor. It also means "shared nothing" between the service and its caller. This is the only architectural model that enables parallel processing in a way that "normal developers" can do the implementation with any realistic hope of success. Actually sharing any meaningful data across threads is beyond the capabilities of most developers. Even most experienced developers mess it up frequently and spend untold hours chasing down race conditions, deadlocks and other nasty issues.
Copyright (c) Marimer LLC