WPF 如何构建线程安全的ObservableCollection?

ilikeff8 2018-12-27 04:54:36

ObservableCollection<Brush> fill_F_Address;
public ObservableCollection<Brush> Fill_F_Address
{
get
{ return fill_F_Address; }
set
{
if (fill_F_Address != value)
{
Interlocked.Exchange(ref fill_F_Address, value);
OnPropertyChanged(nameof(Fill_F_Address));
}
}
}


一个属性,使用ObservableCollection,是的元素变化时可以在界面自动刷新感知,但在多线程中,并不是安全的,
多线程中,将用到
Fill_F_Address[10]=Brushes.Black;
如何使上述语句变得线程安全?
Interlocked.Exchange(ref Fill_F_Address[10]=Brushes.Black);

Interlocked.Exchange(ref fill_F_Address[10]=Brushes.Black);
都是不行的,ref参数不可以是属性或索引器
...全文
1271 19 打赏 收藏 转发到动态 举报
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
空港电脑 2018-12-29
  • 打赏
  • 举报
回复
线程安全有必要进入lock区的
sp1234_maJia 2018-12-28
  • 打赏
  • 举报
回复
实际上,对于业务系统来说,不可能空谈什么编程上的“加锁”代码。卖票时是把一张票从“待售”移动到“正在售”,如果超时没卖出去则放回“待售”库。用这个方式来实现事务。 我上面举例只是用来说明编程上“加锁”要在特定的编程层次,底层纠结加锁是无意义的(因为高层编程必须真正加锁,底层就不必空谈什么“线程安全”了)。只是用售票来举例,把一个长事务多进程销售行为,暂时类比为是短事务进程内多线程销售行为。
sp1234_maJia 2018-12-28
  • 打赏
  • 举报
回复
引用 9 楼 ilikeff8 的回复:
lock有可能开销太大了
我给你举一个例子,假设我们上订票网站买高铁票,那么出票的一断时间(例如20分钟)这张票必定是不能同时卖给别人的,也就是说“出票、卖票、(未卖出)返回销售”要放到同一个事务中进行保护。这可以类比为”线程安全“。当然这其实是进程安全,但是这里的"安全"概念是一致的。 那么这个安全是由调用 Add 和 读取数据的宿主客户代码来保障的,也就是要写 lock 语句的。那么你说你不在卖票系统的层面去加锁,偏要纠结于什么“出票行为跟自己加锁、卖票行为跟自己加锁、返回销售行为跟自己加锁”,那么此时根本不懂事务加锁概念,不是在应有的层次去加锁,只不过是多余的、空谈什么下一级细节去无效果地加锁。这时候这种锁就是多余的代码。
  • 打赏
  • 举报
回复
加上 lockSlim 就线程安全了?那么此时什么叫做"线程不安全"呢? 请举一个例子来说明你这样写代码到底有什么必要?!
  • 打赏
  • 举报
回复
说白了,这里的所谓“线程安全的 ObservableCollection”纯粹是一个偷换概念的说法。
  • 打赏
  • 举报
回复
你的过程中,当你调用 Add 方法放入某对象之后,当你再用 Item 索引器读取同一下标位置的对象时,由于多线程并发原因,那么此时完全可能是幻象读取对象,这样你的这个过程根本不是什么“线程安全的”。所以你在楼上贴出来的代码其实根本谈不上“线程安全”。
ilikeff8 2018-12-28
  • 打赏
  • 举报
回复
写了个ThreadSafeObservableCollection
WPF线程安全的ObservableCollection
ilikeff8 2018-12-28
  • 打赏
  • 举报
回复
而且1-11的步骤有软硬件的操作,顺序是严格不可更改的
ilikeff8 2018-12-28
  • 打赏
  • 举报
回复
引用 14 楼 sp1234_maJia 的回复:
[quote=引用 9 楼 ilikeff8 的回复:]

lock有可能开销太大了


我给你举一个例子,假设我们上订票网站买高铁票,那么出票的一断时间(例如20分钟)这张票必定是不能同时卖给别人的,也就是说“出票、卖票、(未卖出)返回销售”要放到同一个事务中进行保护。这可以类比为”线程安全“。当然这其实是进程安全,但是这里的"安全"概念是一致的。

那么这个安全是由调用 Add 和 读取数据的宿主客户代码来保障的,也就是要写 lock 语句的。那么你说你不在卖票系统的层面去加锁,偏要纠结于什么“出票行为跟自己加锁、卖票行为跟自己加锁、返回销售行为跟自己加锁”,那么此时根本不懂事务加锁概念,不是在应有的层次去加锁,只不过是多余的、空谈什么下一级细节去无效果地加锁。这时候这种锁就是多余的代码。[/quote]

的确是没想到完全线程安全的办法,ThreadSafeObservableCollection目前暂时工作良好,此外业务逻辑上协助防止了一些多线程高并发问题

因为业务逻辑里已经有个外围lock控制其他流程,l如果a,b,c(都是ObservableCollection) 再lock,就是这样了

static object lock_other=new object();
static object lock_a=new object();
static object lock_b=new object();
static object lock_c=new object();

lock (lock_other)
{
// 1 todo
lock (lock_a)
{
// 2 todo in lock
a[0]=...
}
// 3 todo
//4 todo
lock (lock_b)
{
// 5 todo in lock
b[0]=...
}
...
//10 todo
lock (lock_a)
{
// 11 todo in lock
a[0]=...
}
}

lock锁太多,测试在N2800 CPU的工控机上。进出lock锁需要几百毫秒

或用一个锁

static object lock_other=new object();
static object lock_x=new object();

lock (lock_other)
{
// 1 todo
lock (lock_x)
{
// 2 todo in lock
a[0]=...

// 3 todo
//4 todo

// 5 todo in lock
b[0]=...
...
//10 todo

// 11 todo in lock
a[0]=...
}


但没有必要进入lock区的3,4,10也被加锁了
ilikeff8 2018-12-28
  • 打赏
  • 举报
回复
引用 11 楼 以专业开发人员为伍 的回复:
你的过程中,当你调用 Add 方法放入某对象之后,当你再用 Item 索引器读取同一下标位置的对象时,由于多线程并发原因,那么此时完全可能是幻象读取对象,这样你的这个过程根本不是什么“线程安全的”。所以你在楼上贴出来的代码其实根本谈不上“线程安全”。


因此 #10 里不重载索引器了,改为重载 SetItem方法
ilikeff8 2018-12-27
  • 打赏
  • 举报
回复
引用 6 楼 以专业开发人员为伍 的回复:
操作集合中某一个单元,跟 Fill_F_Address 属性有什么关系?怎么会在这个属性上写代码?各个子线程中冲突赋值一个变量/单元,本来就是会有这个结果。赋值是正常的操作,本身没有任何“线程安全”问题,ObservableCollection<> 也没有任何“线程安全”问题。

其实只是你自己写的方法才有“线程安全问题”。那么你应该 lock 同步操作。但是这是你自己的代码的问题,并不存在什么“线程安全的ObservableCollection”。明明是你自己的业务处理过程的线程安全问题,怎么偷换概念为“线程安全的 ObservableCollection”呢?所以肯定是你根本没搞懂什么叫做“线程安全”。


lock有可能开销太大了,没想到在工控机上跑的,性能一般,否则就用winform做了,现在是做写速度上的优化

现在暂时
public class ObservableBrushCollection
{
readonly ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim();
ObservableCollection<Brush> valueList = new ObservableCollection<Brush>();

public Brush this[int index]
{
get
{
return valueList[index];
}
set
{
lockSlim.EnterWriteLock();

try
{
valueList[index] = value;
}
finally
{
lockSlim.ExitWriteLock();
}
}
}

public void Add(Brush brush)
{
lockSlim.EnterWriteLock();

try
{
valueList.Add(brush);
}
finally
{
lockSlim.ExitWriteLock();
}
}
}
  • 打赏
  • 举报
回复
怎会跟 UI 捆绑在一起死锁呢?
  • 打赏
  • 举报
回复
引用 4 楼 ilikeff8 的回复:
[quote=引用 2 楼 exception1992 的回复:] WPF 程序是单线程单元模式,也就是STA。想在使用多线程的时候操作属性应该用WPF中的Dispatcher对象,它允许跨线程去操作WPF控件,集合等等。
只能这样吗 App.Current.Dispatcher.Invoke(() => { Fill_B_Address[10] = Brushes.Black; }); 这个和winform的调用委托类似,会不会开销有点大,因为这种变量一个线程里有好几处赋值 那和lock(ob){...}比哪个开销小点 [/quote] 给一个集合的单元赋值,跟会跟 UI 捆绑在一起死锁呢?
  • 打赏
  • 举报
回复
操作集合中某一个单元,跟 Fill_F_Address 属性有什么关系?怎么会在这个属性上写代码?各个子线程中冲突赋值一个变量/单元,本来就是会有这个结果。赋值是正常的操作,本身没有任何“线程安全”问题,ObservableCollection<> 也没有任何“线程安全”问题。 其实只是你自己写的方法才有“线程安全问题”。那么你应该 lock 同步操作。但是这是你自己的代码的问题,并不存在什么“线程安全的ObservableCollection”。明明是你自己的业务处理过程的线程安全问题,怎么偷换概念为“线程安全的 ObservableCollection”呢?所以肯定是你根本没搞懂什么叫做“线程安全”。
ilikeff8 2018-12-27
  • 打赏
  • 举报
回复
本想仿照这种写法,用读写锁
https://github.com/piercep/thread-safe-collections/blob/master/ThreadSafeCollections/ThreadSafeCollections/TList.cs
把所有List的地方改成ObservableCollection,写个TObservableCollection,但写完发现界面绑定后没智能感应了,好像不能简单处理
ilikeff8 2018-12-27
  • 打赏
  • 举报
回复
引用 2 楼 exception1992 的回复:
WPF 程序是单线程单元模式,也就是STA。想在使用多线程的时候操作属性应该用WPF中的Dispatcher对象,它允许跨线程去操作WPF控件,集合等等。


只能这样吗
App.Current.Dispatcher.Invoke(() =>
{
Fill_B_Address[10] = Brushes.Black;
});
这个和winform的调用委托类似,会不会开销有点大,因为这种变量一个线程里有好几处赋值
那和lock(ob){...}比哪个开销小点
ilikeff8 2018-12-27
  • 打赏
  • 举报
回复
Interlocked.Exchange(ref Fill_F_Address[10],Brushes.Black);

Interlocked.Exchange(ref fill_F_Address[10],Brushes.Black);
exception92 2018-12-27
  • 打赏
  • 举报
回复
WPF 程序是单线程单元模式,也就是STA。想在使用多线程的时候操作属性应该用WPF中的Dispatcher对象,它允许跨线程去操作WPF控件,集合等等。
ilikeff8 2018-12-27
  • 打赏
  • 举报
回复
if (fill_F_Address != value) // 不关心是不是判断后是否刚好其他线程修改了值,允许脏读
{
Interlocked.Exchange(ref fill_F_Address, value);
OnPropertyChanged(nameof(Fill_F_Address));
}
在Windows系统中,notepad.exe(记事本)是一个“经典的”、“简洁的”文本编辑器。这个软件,没有华丽的外观,也没有繁杂的功能,仅仅是一个文本编辑小软件。虽然经过Windows系统数十年的变换,但它却保持着永恒姿态,数十年来几乎不曾改变过。曾经,VS中的经典DEMO中,就有它的身影,一个新建的项目,就藏有一个新建的“记事本”。然而,在WPF的项目中,“记事本”却消失的无影无踪,也许是很容易实现,也许是为了革新,而不愿再传承“经典”。确实,使用WPF技术再次让“记事本”复活,确实也是一件非常容易的事情。但是,如果,使用WPF技术,再搭配当下非常流行的MVVM模式呢?复活“记事本”的难度却陡然上升至很多WPF程序员为之默默叹气。而,MVVM模式是掌握WPF的最顶级技术,MVVM模式拥有的无尽的优势,让WPF相对于过往的编程模式来说,是一种革命性的创新,从而也成为大中型WPF项目中必须的模式。但,学习难度。。。。。。在这个《WPF记事本开发详解》的课程中,赵老师带领你在WPF中,从零开始一步步构建MVVM模式,直到让你亲自以WPF+MVVM的方式,让这个经典的“记事本”软件从你的手中“复活”。在课程中,赵老师会详细讲解WPF和MVVM中的各种技巧,让你从此爱上WPF+MVVM编程。

110,538

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧