For example, I've got a business object Person
:
class Person : INotifyPropertyChanged
{
string Name { get; set; }
DateTime DateOfBirth { get; set; }
}
// ^ abbreviated for better legibility; implementation would be trivial
And I've got some Winforms UI controls data-bound to an object of this class:
Person somePerson = ...;
nameTextBox.DataBindings.Add("Text", somePerson, "Name");
dobDatePicker.DataBindings.Add("Value", somePerson, "DateOfBirth");
Now I am making changes to somePerson
and thanks to having INotifyPropertyChanged
implemented, those changes are reflected in the UI. So far so good.
Now to my problem: If I make changes to somePerson
in a worker thread (ie. not in the UI thread), e.g. because I'm loading data from a DB as a background operation, this might cause exceptions because the data binding attempts to update the controls, which is only allowed to happen on the UI thread.
This means that I need to call InvokeRequired
on a UI element to see if I'm allowed to update a business object — which seems like a violation of the application's logical layering.
Ideally, I want to be able to modify my business objects without having to care whether it is data-bound to the UI or not. Is this somehow possible with Winforms data binding, or not?
This doesn't answer your question, but I try as far as possible to avoid this problem.
Because of the existence of data binding, I make sure the only code that can update business objects is code that's running on the GUI thread.
For async operations, I adopt a pattern of:
I can recommend the
System.Threading.Tasks.Task
class as an abstraction around most of the above steps. It's new in .NET 4.0, but it's also available as a separate download for .NET 3.5 apps.Steps (1) and (2) are what the
Task
class does without any customisation. You can achieve (3) by spawning a separateTask
from inside the background thread and specifyingTaskScheduler.FromCurrentSynchronizationContext
as a scheduler. (You'll need to callFromCurrentSynchronizationContext
from the GUI thread, in step (1), not from the background thread.)