我必须创建一个 WPF UI,它订阅实时外汇汇率(货币 + 汇率)更新并将其显示在网格中(每秒大约 1000 次更新,这意味着网格中的每一行都可以更新upto每秒 1000 次)。网格在任何时间点都至少有 50 行。
为此,我创建了一个订阅更新事件的 Viewmodel,并将这些更新存储在并发字典中,其中键作为符号,值作为 RateViewModel 对象。然后我有另一个可观察的集合,其中包含所有这些rateviewmodel 对象,并将其绑定到网格。
Code:
public class MyViewModel
{
private readonly IRatesService ratesService;
private readonly ConcurrentDictionary<string, RateViewModel> rateDictionary;
private object _locker = new object();
public MyViewModel(IRatesService ratesService)
{
this.ratesService = ratesService;
this.ratesService.OnUpdate += OnUpdate;
rateDictionary = new ConcurrentDictionary<string, RateViewModel>();
RateViewModels = new ObservableCollection<RateViewModel>();
}
private void OnUpdate(object sender, RateUpdateEventArgs e)
{
RateViewModel exisistingRate;
if (!rateDictionary.TryGetValue(e.Update.Currency, out exisistingRate))
{
exisistingRate = new RateViewModel(new Rate(e.Update.Currency, e.Update.Rate));
rateDictionary.TryAdd(e.Update.Currency, exisistingRate);
return;
}
lock (_locker)
{
exisistingRate.UpdateRate(e.Update.Rate);
}
Application.Current.Dispatcher.BeginInvoke(new Action(() => SearchAndUpdate(exisistingRate)));
}
public ObservableCollection<RateViewModel> RateViewModels { get; set; }
private void SearchAndUpdate(RateViewModel rateViewModel)
{
//Equals is based on Currency
if (!RateViewModels.Contains(rateViewModel))
{
RateViewModels.Add(rateViewModel);
return;
}
var index = RateViewModels.IndexOf(rateViewModel);
RateViewModels[index] = rateViewModel;
}
}
我对此有4个问题:
有没有一种方法可以消除 ObservableCollection,因为它会导致 2 个不同的数据结构存储相同的项目 - 但仍然将我的更新转发到 UI?
我使用了并发字典,这导致锁定整个更新操作。除了锁定整个字典或任何数据结构之外,还有其他聪明的方法来处理这个问题吗?
我的 UpdateRate 方法也锁定 - 我的 RateviewModel 上的所有属性都是只读的,除了价格之外,因为它正在更新。有没有办法使这个原子化,请注意,价格是双倍的。
有没有办法可以优化 SearchAndUpdate 方法,这与第一个相关。目前我相信这是一个 O(n) 操作。
Using .NET 4.0为简洁起见,省略了 INPC。
*EDIT:*您能否帮我以更好的方式重写此内容,同时考虑到所有 4 点?伪代码就可以了。
谢谢,
-麦克风
1) 我不会担心 50 个额外的裁判
2)是的,无锁数据结构是可行的。联锁 http://msdn.microsoft.com/en-us/library/system.threading.interlocked.aspx你的朋友在这里吗?他们几乎都是一次性的。读写锁 http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx如果您不经常更改字典中的项目,那么这是另一个不错的选择。
3) 一般来说,如果您要处理的数据多于 UI 可以处理的数据,您将需要在后台进行更新,仅在 UI 线程上触发 INPC,更重要的是具有删除 UI 更新的功能(同时仍在更新支持字段)。基本方法如下:
- Do an
Interlocked.Exchange
在支持场上
- Use
Interlocked.CompareExchange
将私有字段设置为 1,如果返回 1 则退出,因为仍有待处理的 UI 更新
- If
Interlocked.CompareExchange
返回 0,调用 UI 并触发属性更改事件并将限制字段更新为 0(从技术上讲,如果您关心非 x86,则需要执行更多操作)
4) SearchAndUpdate
看来是多余了...UpdateRate
应该冒泡到 UI,并且如果需要向可观察集合添加或删除项目,则只需要调用 UI 线程。
更新:这是一个示例实现...事情稍微复杂一点,因为您使用的是双精度数,它在 32 位 CPU 上无法免费获得原子性。
class MyViewModel : INotifyPropertyChanged
{
private System.Windows.Threading.Dispatcher dispatcher;
public MyViewModel(System.Windows.Threading.Dispatcher dispatcher)
{
this.dispatcher = dispatcher;
}
int myPropertyUpdating; //needs to be marked volatile if you care about non x86
double myProperty;
double MyPropery
{
get
{
// Hack for Missing Interlocked.Read for doubles
// if you are compiled for 64 bit you should be able to just do a read
var retv = Interlocked.CompareExchange(ref myProperty, myProperty, -myProperty);
return retv;
}
set
{
if (myProperty != value)
{
// if you are compiled for 64 bit you can just do an assignment here
Interlocked.Exchange(ref myProperty, value);
if (Interlocked.Exchange(ref myPropertyUpdating, 1) == 0)
{
dispatcher.BeginInvoke(() =>
{
try
{
PropertyChanged(this, new PropertyChangedEventArgs("MyProperty"));
}
finally
{
myPropertyUpdating = 0;
Thread.MemoryBarrier(); // This will flush the store buffer which is the technically correct thing to do... but I've never had problems with out it
}
}, null);
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate {};
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)