开发者

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:

  1. 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)
  2. 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.
  3. 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.)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜