我正在设计/重新编写应用程序的数据绑定部分,该应用程序大量使用来自后台线程的winforms数据绑定和更新(每秒一次> 100条记录).
让我们假设应用程序是一个股票交易应用程序,后台线程监视数据更改并将它们放到数据对象上.这些对象存储在一个BindingList<>
并实现,INotifyPropertyChanged
以通过数据绑定将更改传播到winforms控件.此外,数据对象当前正在通过WinformsSynchronizationContext.Send
UI线程编组更改.用户可以在UI中输入一些值,这意味着可以从两侧更改某些值.并且更新不应该超出用户值.
所以我想到了几个问题:
是否有一般设计 - 指导如何做到这一点(数据绑定中的背景更新)?
何时以及如何在UI线程上编组?
后台线程与绑定/数据对象交互的最佳方式是什么?
应该使用哪些类/接口?(BindingSource,...)
...
用户界面实际上并不知道有一个后台线程,它会更新控件,而且根据我在数据绑定场景中的理解,用户界面不应该知道数据的来源......您可以将后台线程视为将数据推送到UI的东西,所以我不确定背景工作者是否是我正在搜索的选项.
有时您希望在数据/业务对象的操作期间获得一些UI响应(例如,在重新计算期间设置背景).在绑定到后台的状态属性上提升属性是不够的,因为控制在计算完成后重新绘制了?我的想法是挂钩propertychanged事件并在控件上调用.update()......还有其他想法吗?
这是一个难题,因为大多数"解决方案"导致大量自定义代码和大量调用BeginInvoke()
或System.ComponentModel.BackgroundWorker
(它本身只是一个薄的包装BeginInvoke
).
在过去,我还发现您很快就希望延迟发送您的INotifyPropertyChanged
活动,直到数据稳定.处理一个适当变更事件的代码通常需要阅读其他属性.你经常有一个控件需要在许多属性之一的状态发生变化时重绘自己,并且你不想让控件过于频繁地重绘.
首先,每个自定义WinForms控件应该读取它在PropertyChanged
事件处理程序中绘制自身所需的所有数据,因此当它是a WM_PAINT
(OnPaint
)消息时不需要锁定任何数据对象.控件不应在获取新数据时立即重新绘制; 相反,它应该打电话Control.Invalidate()
.Windows会将WM_PAINT
消息组合成尽可能少的请求,并且只在UI线程没有其他任何操作时才发送它们.这样可以最大限度地减少重绘次数和数据对象锁定的时间.(无论如何,标准控件主要使用数据绑定执行此操作)
数据对象需要记录更改后的更改内容,然后在完成一组更改后,"踢"UI线程调用SendChangeEvents
方法,然后调用PropertyChanged
所有属性的事件处理程序(在UI线程上)那已经改变了.在该SendChangeEvents()
方法运行时,必须锁定数据对象以阻止后台线程更新它们.
BeginInvoke
只要有一组更新从数据库中读取bean,就可以通过调用来"踢"UI线程.通常使用计时器进行UI线程轮询更好,因为Windows仅WM_TIMER
在UI消息队列为空时发送消息,从而导致UI感觉更具响应性.
还要考虑根本不使用数据绑定,并让UI在每次计时器触发时询问每个数据对象"发生了什么变化".数据绑定总是很好看,但很快就会成为问题的一部分,而不是解决方案的一部分.
由于数据对象的锁定/解锁很麻烦,并且可能无法足够快地从数据库中读取更新,因此您可能希望将UI线程传递给数据对象的(虚拟)副本.使数据对象具有持久性/不可变性,以便对数据对象的任何更改返回新数据对象而不是更改当前数据对象都可以启用此功能.
持久对象的声音很慢,但不必,看到这和那一些指针.另请参阅Stack Overflow 上的这个和那个.
还要看一下retlang - .NET中基于消息的并发性.它的消息批处理可能很有用.
(对于WPF,我会在UI线程中设置一个View-Model,然后由后台线程从多线程模型中"批量"更新.但是,WPF在组合数据绑定事件和WinForms方面要好得多.)