Winforms data-binding to business objects in a multi-threaded scenario without InvokeRequired?
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, th开发者_StackOverflowose 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:
- Send work to the background: Trigger an async operation -- say, a thread pool item -- from the GUI thread. Pass only plain data types into the thread pool, and receive only plain data types back. If business objects are used by the thread pool, make sure that these are either brand new ones (that haven't been data bound yet), or are clones of the originals (to avoid concurrent access)
- Do the work and obtain a result: Execute the async operation on a background thread. The background thread code will own the 'safe' objects given to it by the GUI; it won't have any interaction with the rest of the application.
- Unpack the result in the GUI: When the async operation is finished, it triggers an 'I am complete' event on the GUI thread. In response, the GUI thread can unpackage any results from the background operation and merge them back into the main business objects, safe in the knowledge that it won't be dealing with concurrent access.
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 separate Task
from inside the background thread and specifying TaskScheduler.FromCurrentSynchronizationContext
as a scheduler. (You'll need to call FromCurrentSynchronizationContext
from the GUI thread, in step (1), not from the background thread.)
精彩评论