C#多线程编程系列(五)- C# ConcurrentBag的实现原理

目录

  • 一、前言
  • 二、ConcurrentBag类
  • 三、 ConcurrentBag线程安全实现原理
    • 1. ConcurrentBag的私有字段
    • 2. 用于数据存储的ThreadLocalList类
    • 3. ConcurrentBag实现新增元素
    • 4. ConcurrentBag 如何实现迭代器模式
  • 四、总结
  • 笔者水平有限,如果错误欢迎各位批评指正!


一、前言#

笔者最近在做一个项目,项目中为了提升吞吐量,使用了消息队列,中间实现了生产消费模式,在生产消费者模式中需要有一个集合,来存储生产者所生产的物品,笔者使用了最常见的List<T>集合类型。

由于生产者线程有很多个,消费者线程也有很多个,所以不可避免的就产生了线程同步的问题。开始笔者是使用lock关键字,进行线程同步,但是性能并不是特别理想,然后有网友说可以使用SynchronizedList<T>来代替使用List<T>达到线程安全的目的。于是笔者就替换成了SynchronizedList<T>,但是发现性能依旧糟糕,于是查看了SynchronizedList<T>的源代码,发现它就是简单的在List<T>提供的API的基础上加了lock,所以性能基本与笔者实现方式相差无几。

最后笔者找到了解决的方案,使用ConcurrentBag<T>类来实现,性能有很大的改观,于是笔者查看了ConcurrentBag<T>的源代码,实现非常精妙,特此在这记录一下。

二、ConcurrentBag类#

ConcurrentBag<T>实现了IProducerConsumerCollection<T>接口,该接口主要用于生产者消费者模式下,可见该类基本就是为生产消费者模式定制的。然后还实现了常规的IReadOnlyCollection<T>类,实现了该类就需要实现IEnumerable<T>、IEnumerable、 ICollection类。

ConcurrentBag<T>对外提供的方法没有List<T>那么多,但是同样有Enumerable实现的扩展方法。类本身提供的方法如下所示。

名称说明
Add将对象添加到 ConcurrentBag 中。
CopyTo从指定数组索引开始,将 ConcurrentBag 元素复制到现有的一维 Array 中。
Equals(Object)确定指定的 Object 是否等于当前的 Object。 (继承自 Object。)
Finalize允许对象在“垃圾回收”回收之前尝试释放资源并执行其他清理操作。 (继承自 Object。)
GetEnumerator返回循环访问 ConcurrentBag 的枚举器。
GetHashCode用作特定类型的哈希函数。 (继承自 Object。)
GetType获取当前实例的 Type。 (继承自 Object。)
MemberwiseClone创建当前 Object 的浅表副本。 (继承自 Object。)
ToArray将 ConcurrentBag 元素复制到新数组。
ToString返回表示当前对象的字符串。 (继承自 Object。)
TryPeek尝试从 ConcurrentBag 返回一个对象但不移除该对象。
TryTake尝试从 ConcurrentBag 中移除并返回对象。

三、 ConcurrentBag线程安全实现原理#

1. ConcurrentBag的私有字段#

ConcurrentBag线程安全实现主要是通过它的数据存储的结构和细颗粒度的锁。

 

Copy

public class ConcurrentBag<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T> { // ThreadLocalList对象包含每个线程的数据 ThreadLocal<ThreadLocalList> m_locals; // 这个头指针和尾指针指向中的第一个和最后一个本地列表,这些本地列表分散在不同线程中 // 允许在线程局部对象上枚举 volatile ThreadLocalList m_headList, m_tailList; // 这个标志是告知操作线程必须同步操作 // 在GlobalListsLock 锁中 设置 bool m_needSync; }

首选我们来看它声明的私有字段,其中需要注意的是集合的数据是存放在ThreadLocal线程本地存储中的。也就是说访问它的每个线程会维护一个自己的集合数据列表,一个集合中的数据可能会存放在不同线程的本地存储空间中,所以如果线程访问自己本地存储的对象,那么是没有问题的,这就是实现线程安全的第一层,使用线程本地存储数据

然后可以看到ThreadLocalList m_headList, m_tailList;这个是存放着本地列表对象的头指针和尾指针,通过这两个指针,我们就可以通过遍历的方式来访问所有本地列表。它使用volatile修饰,不允许线程进行本地缓存,每个线程的读写都是直接操作在共享内存上,这就保证了变量始终具有一致性。任何线程在任何时间进行读写操作均是最新值。对于volatile修饰符,感谢我是攻城狮指出描述错误。

最后又定义了一个标志,这个标志告知操作线程必须进行同步操作,这是实现了一个细颗粒度的锁,因为只有在几个条件满足的情况下才需要进行线程同步。

2. 用于数据存储的ThreadLocalList类#

接下来我们来看一下ThreadLocalList类的构造,该类就是实际存储了数据的位置。实际上它是使用双向链表这种结构进行数据存储。

 

Copy

[Serializable] // 构造了双向链表的节点 internal class Node { public Node(T value) { m_value = value; } public readonly T m_value; public Node m_next; public Node m_prev; } /// <summary> /// 集合操作类型 /// </summary> internal enum ListOperation { None, Add, Take }; /// <summary> /// 线程锁定的类 /// </summary> internal class ThreadLocalList { // 双向链表的头结点 如果为null那么表示链表为空 internal volatile Node m_head; // 双向链表的尾节点 private volatile Node m_tail; // 定义当前对List进行操作的种类 // 与前面的 ListOperation 相对应 internal volatile int m_currentOp; // 这个列表元素的计数 private int m_count; // The stealing count // 这个不是特别理解 好像是在本地列表中 删除某个Node 以后的计数 internal int m_stealCount; // 下一个列表 可能会在其它线程中 internal volatile ThreadLocalList m_nextList; // 设定锁定是否已进行 internal bool m_lockTaken; // The owner thread for this list internal Thread m_ownerThread; // 列表的版本,只有当列表从空变为非空统计是底层 internal volatile int m_version; /// <summary> /// ThreadLocalList 构造器 /// </summary> /// <param name="ownerThread">拥有这个集合的线程</param> internal ThreadLocalList(Thread ownerThread) { m_ownerThread = ownerThread; } /// <summary> /// 添加一个新的item到链表首部 /// </summary> /// <param name="item">The item to add.</param> /// <param name="updateCount">是否更新计数.</param> internal void Add(T item, bool updateCount) { checked { m_count++; } Node node = new Node(item); if (m_head == null) { Debug.Assert(m_tail == null); m_head = node; m_tail = node; m_version++; // 因为进行初始化了,所以将空状态改为非空状态 } else { // 使用头插法 将新的元素插入链表 node.m_next = m_head; m_head.m_prev = node; m_head = node; } if (updateCount) // 更新计数以避免此添加同步时溢出 { m_count = m_count - m_stealCount; m_stealCount = 0; } } /// <summary> /// 从列表的头部删除一个item /// </summary> /// <param name="result">The removed item</param> internal void Remove(out T result) { // 双向链表删除头结点数据的流程 Debug.Assert(m_head != null); Node head = m_head; m_head = m_head.m_next; if (m_head != null) { m_head.m_prev = null; } else { m_tail = null; } m_count--; result = head.m_value; } /// <summary> /// 返回列表头部的元素 /// </summary> /// <param name="result">the peeked item</param> /// <returns>True if succeeded, false otherwise</returns> internal bool Peek(out T result) { Node head = m_head; if (head != null) { result = head.m_value; return true; } result = default(T); return false; } /// <summary> /// 从列表的尾部获取一个item /// </summary> /// <param name="result">the removed item</param> /// <param name="remove">remove or peek flag</param> internal void Steal(out T result, bool remove) { Node tail = m_tail; Debug.Assert(tail != null); if (remove) // Take operation { m_tail = m_tail.m_prev; if (m_tail != null) { m_tail.m_next = null; } else { m_head = null; } // Increment the steal count m_stealCount++; } result = tail.m_value; } /// <summary> /// 获取总计列表计数, 它不是线程安全的, 如果同时调用它, 则可能提供不正确的计数 /// </summary> internal int Count { get { return m_count - m_stealCount; } } }

从上面的代码中我们可以更加验证之前的观点,就是ConcurentBag<T>在一个线程中存储数据时,使用的是双向链表ThreadLocalList实现了一组对链表增删改查的方法。

3. ConcurrentBag实现新增元素#

接下来我们看一看ConcurentBag<T>是如何新增元素的。

 

Copy

/// <summary> /// 尝试获取无主列表,无主列表是指线程已经被暂停或者终止,但是集合中的部分数据还存储在那里 /// 这是避免内存泄漏的方法 /// </summary> /// <returns></returns> private ThreadLocalList GetUnownedList() { //此时必须持有全局锁 Contract.Assert(Monitor.IsEntered(GlobalListsLock)); // 从头线程列表开始枚举 找到那些已经被关闭的线程 // 将它所在的列表对象 返回 ThreadLocalList currentList = m_headList; while (currentList != null) { if (currentList.m_ownerThread.ThreadState == System.Threading.ThreadState.Stopped) { currentList.m_ownerThread = Thread.CurrentThread; // the caller should acquire a lock to make this line thread safe return currentList; } currentList = currentList.m_nextList; } return null; } /// <summary> /// 本地帮助方法,通过线程对象检索线程线程本地列表 /// </summary> /// <param name="forceCreate">如果列表不存在,那么创建新列表</param> /// <returns>The local list object</returns> private ThreadLocalList GetThreadList(bool forceCreate) { ThreadLocalList list = m_locals.Value; if (list != null) { return list; } else if (forceCreate) { // 获取用于更新操作的 m_tailList 锁 lock (GlobalListsLock) { // 如果头列表等于空,那么说明集合中还没有元素 // 直接创建一个新的 if (m_headList == null) { list = new ThreadLocalList(Thread.CurrentThread); m_headList = list; m_tailList = list; } else { // ConcurrentBag内的数据是以双向链表的形式分散存储在各个线程的本地区域中 // 通过下面这个方法 可以找到那些存储有数据 但是已经被停止的线程 // 然后将已停止线程的数据 移交到当前线程管理 list = GetUnownedList(); // 如果没有 那么就新建一个列表 然后更新尾指针的位置 if (list == null) { list = new ThreadLocalList(Thread.CurrentThread); m_tailList.m_nextList = list; m_tailList = list; } } m_locals.Value = list; } } else { return null; } Debug.Assert(list != null); return list; } /// <summary> /// Adds an object to the <see cref="ConcurrentBag{T}"/>. /// </summary> /// <param name="item">The object to be added to the /// <see cref="ConcurrentBag{T}"/>. The value can be a null reference /// (Nothing in Visual Basic) for reference types.</param> public void Add(T item) { // 获取该线程的本地列表, 如果此线程不存在, 则创建一个新列表 (第一次调用 add) ThreadLocalList list = GetThreadList(true); // 实际的数据添加操作 在AddInternal中执行 AddInternal(list, item); } /// <summary> /// </summary> /// <param name="list"></param> /// <param name="item"></param> private void AddInternal(ThreadLocalList list, T item) { bool lockTaken = false; try { #pragma warning disable 0420 Interlocked.Exchange(ref list.m_currentOp, (int)ListOperation.Add); #pragma warning restore 0420 // 同步案例: // 如果列表计数小于两个, 因为是双向链表的关系 为了避免与任何窃取线程发生冲突 必须获取锁 // 如果设置了 m_needSync, 这意味着有一个线程需要冻结包 也必须获取锁 if (list.Count < 2 || m_needSync) { // 将其重置为None 以避免与窃取线程的死锁 list.m_currentOp = (int)ListOperation.None; // 锁定当前对象 Monitor.Enter(list, ref lockTaken); } // 调用 ThreadLocalList.Add方法 将数据添加到双向链表中 // 如果已经锁定 那么说明线程安全 可以更新Count 计数 list.Add(item, lockTaken); } finally { list.m_currentOp = (int)ListOperation.None; if (lockTaken) { Monitor.Exit(list); } } }

从上面代码中,我们可以很清楚的知道Add()方法是如何运行的,其中的关键就是GetThreadList()方法,通过该方法可以获取当前线程的数据存储列表对象,假如不存在数据存储列表,它会自动创建或者通过GetUnownedList()方法来寻找那些被停止但是还存储有数据列表的线程,然后将数据列表返回给当前线程中,防止了内存泄漏。

在数据添加的过程中,实现了细颗粒度的lock同步锁,所以性能会很高。删除和其它操作与新增类似,本文不再赘述。

4. ConcurrentBag 如何实现迭代器模式#

看完上面的代码后,我很好奇ConcurrentBag<T>是如何实现IEnumerator来实现迭代访问的,因为ConcurrentBag<T>是通过分散在不同线程中的ThreadLocalList来存储数据的,那么在实现迭代器模式时,过程会比较复杂。

后面再查看了源码之后,发现ConcurrentBag<T>为了实现迭代器模式,将分在不同线程中的数据全都存到一个List<T>集合中,然后返回了该副本的迭代器。所以每次访问迭代器,它都会新建一个List<T>的副本,这样虽然浪费了一定的存储空间,但是逻辑上更加简单了。

 

Copy

/// <summary> /// 本地帮助器方法释放所有本地列表锁 /// </summary> private void ReleaseAllLocks() { // 该方法用于在执行线程同步以后 释放掉所有本地锁 // 通过遍历每个线程中存储的 ThreadLocalList对象 释放所占用的锁 ThreadLocalList currentList = m_headList; while (currentList != null) { if (currentList.m_lockTaken) { currentList.m_lockTaken = false; Monitor.Exit(currentList); } currentList = currentList.m_nextList; } } /// <summary> /// 从冻结状态解冻包的本地帮助器方法 /// </summary> /// <param name="lockTaken">The lock taken result from the Freeze method</param> private void UnfreezeBag(bool lockTaken) { // 首先释放掉 每个线程中 本地变量的锁 // 然后释放全局锁 ReleaseAllLocks(); m_needSync = false; if (lockTaken) { Monitor.Exit(GlobalListsLock); } } /// <summary> /// 本地帮助器函数等待所有未同步的操作 /// </summary> private void WaitAllOperations() { Contract.Assert(Monitor.IsEntered(GlobalListsLock)); ThreadLocalList currentList = m_headList; // 自旋等待 等待其它操作完成 while (currentList != null) { if (currentList.m_currentOp != (int)ListOperation.None) { SpinWait spinner = new SpinWait(); // 有其它线程进行操作时,会将cuurentOp 设置成 正在操作的枚举 while (currentList.m_currentOp != (int)ListOperation.None) { spinner.SpinOnce(); } } currentList = currentList.m_nextList; } } /// <summary> /// 本地帮助器方法获取所有本地列表锁 /// </summary> private void AcquireAllLocks() { Contract.Assert(Monitor.IsEntered(GlobalListsLock)); bool lockTaken = false; ThreadLocalList currentList = m_headList; // 遍历每个线程的ThreadLocalList 然后获取对应ThreadLocalList的锁 while (currentList != null) { // 尝试/最后 bllock 以避免在获取锁和设置所采取的标志之间的线程港口 try { Monitor.Enter(currentList, ref lockTaken); } finally { if (lockTaken) { currentList.m_lockTaken = true; lockTaken = false; } } currentList = currentList.m_nextList; } } /// <summary> /// Local helper method to freeze all bag operations, it /// 1- Acquire the global lock to prevent any other thread to freeze the bag, and also new new thread can be added /// to the dictionary /// 2- Then Acquire all local lists locks to prevent steal and synchronized operations /// 3- Wait for all un-synchronized operations to be done /// </summary> /// <param name="lockTaken">Retrieve the lock taken result for the global lock, to be passed to Unfreeze method</param> private void FreezeBag(ref bool lockTaken) { Contract.Assert(!Monitor.IsEntered(GlobalListsLock)); // 全局锁定可安全地防止多线程调用计数和损坏 m_needSync Monitor.Enter(GlobalListsLock, ref lockTaken); // 这将强制同步任何将来的添加/执行操作 m_needSync = true; // 获取所有列表的锁 AcquireAllLocks(); // 等待所有操作完成 WaitAllOperations(); } /// <summary> /// 本地帮助器函数返回列表中的包项, 这主要由 CopyTo 和 ToArray 使用。 /// 这不是线程安全, 应该被称为冻结/解冻袋块 /// 本方法是私有的 只有使用 Freeze/UnFreeze之后才是安全的 /// </summary> /// <returns>List the contains the bag items</returns> private List<T> ToList() { Contract.Assert(Monitor.IsEntered(GlobalListsLock)); // 创建一个新的List List<T> list = new List<T>(); ThreadLocalList currentList = m_headList; // 遍历每个线程中的ThreadLocalList 将里面的Node的数据 添加到list中 while (currentList != null) { Node currentNode = currentList.m_head; while (currentNode != null) { list.Add(currentNode.m_value); currentNode = currentNode.m_next; } currentList = currentList.m_nextList; } return list; } /// <summary> /// Returns an enumerator that iterates through the <see /// cref="ConcurrentBag{T}"/>. /// </summary> /// <returns>An enumerator for the contents of the <see /// cref="ConcurrentBag{T}"/>.</returns> /// <remarks> /// The enumeration represents a moment-in-time snapshot of the contents /// of the bag. It does not reflect any updates to the collection after /// <see cref="GetEnumerator"/> was called. The enumerator is safe to use /// concurrently with reads from and writes to the bag. /// </remarks> public IEnumerator<T> GetEnumerator() { // Short path if the bag is empty if (m_headList == null) return new List<T>().GetEnumerator(); // empty list bool lockTaken = false; try { // 首先冻结整个 ConcurrentBag集合 FreezeBag(ref lockTaken); // 然后ToList 再拿到 List的 IEnumerator return ToList().GetEnumerator(); } finally { UnfreezeBag(lockTaken); } }

由上面的代码可知道,为了获取迭代器对象,总共进行了三步主要的操作。

  1. 使用FreezeBag()方法,冻结整个ConcurrentBag<T>集合。因为需要生成集合的List<T>副本,生成副本期间不能有其它线程更改损坏数据。
  2. ConcurrrentBag<T>生成List<T>副本。因为ConcurrentBag<T>存储数据的方式比较特殊,直接实现迭代器模式困难,考虑到线程安全和逻辑,最佳的办法是生成一个副本。
  3. 完成以上操作以后,就可以使用UnfreezeBag()方法解冻整个集合。

那么FreezeBag()方法是如何来冻结整个集合的呢?也是分为三步走。

  1. 首先获取全局锁,通过Monitor.Enter(GlobalListsLock, ref lockTaken);这样一条语句,这样其它线程就不能冻结集合。
  2. 然后获取所有线程中ThreadLocalList的锁,通过`AcquireAllLocks()方法来遍历获取。这样其它线程就不能对它进行操作损坏数据。
  3. 等待已经进入了操作流程线程结束,通过WaitAllOperations()方法来实现,该方法会遍历每一个ThreadLocalList对象的m_currentOp属性,确保全部处于None操作。

完成以上流程后,那么就是真正的冻结了整个ConcurrentBag<T>集合,要解冻的话也类似。在此不再赘述。

四、总结#

下面给出一张图,描述了ConcurrentBag<T>是如何存储数据的。通过每个线程中的ThreadLocal来实现线程本地存储,每个线程中都有这样的结构,互不干扰。然后每个线程中的m_headList总是指向ConcurrentBag<T>的第一个列表,m_tailList指向最后一个列表。列表与列表之间通过m_locals 下的 m_nextList相连,构成一个单链表。

数据存储在每个线程的m_locals中,通过Node类构成一个双向链表。
PS: 要注意m_tailListm_headList并不是存储在ThreadLocal中,而是所有的线程共享一份。

1534581126728

以上就是有关ConcurrentBag<T>类的实现,笔者的一些记录和解析。

笔者水平有限,如果错误欢迎各位批评指正!

附上ConcurrentBag<T>源码地址:戳一戳

作者:InCerry

出处:https://www.cnblogs.com/InCerry/p/9497729.html

版权:本文采用「署名 4.0 国际」知识共享许可协议进行许可。

 

https://www.cnblogs.com/fancunwei/p/9442469.html

orleans源码里面有用过许多和线程/队列相关的类,
System.Collections.Concurrent下面的类,ConcurrentQueue和ConcurrentDictionary等,还有Interlocked/BlockingCollection等。

@ 它使用volatile修饰,所以它是线程安全的。有误吧,volatile并不保证线程安全。
可能这句话笔者描述有遗漏,因为多个线程同时访问一个变量,允许每个线程进行本地缓存,这就导致了变量的不一致性。volatile修饰的变量,不允许线程进行本地缓存,每个线程的读写都是直接操作在共享内存上,这就保证了变量始终具有一致性。引用指针的大小和CPU位数一般都是一致,所以操作使原子性的,线程安全的。

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438223.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#多线程编程系列(五)- 浅析C# Dictionary实现原理

目录 一、前言二、理论知识 1、Hash算法2、Hash桶算法3、解决冲突算法三、Dictionary实现 1. Entry结构体2. 其它关键私有变量3. Dictionary - Add操作4. Dictionary - Find操作5. Dictionary - Remove操作6. Dictionary - Resize操作(扩容)7. Dictionary - 再谈Add操作8. Col…

ASP.NET MVC 入门5、View与ViewData

本系列文章基于ASP.NET MVC Preview5. view在MVC模式中与用户进行最直接的接触&#xff0c;它负责数据的呈现。这里要注意一点就是&#xff0c;view只是负责数据的呈现&#xff0c;所以我们应该要尽量让view中不涉及业务逻辑的处理。 我们来添加一个Blog首页的view。在安装了…

ASP.NET MVC 入门6、TempData

本系列文章基于ASP.NET MVC Preview5. ASP.NET MVC的TempData用于传输一些临时的数据&#xff0c;例如在各个控制器Action间传递临时的数据或者给View传递一些临时的数据&#xff0c;相信大家都看过“在ASP.NET页面间传值的方法有哪几种”这个面试题&#xff0c;在ASP.NET MVC…

ASP.NET MVC 入门11、使用AJAX

本系列文章基于ASP.NET MVC beta.本示例Blog系统同步更新的演示站点&#xff1a;http://4mvcblog.qsh.in/ 在ASP.NET MVC beta发布之前&#xff0c;M$就宣布支持开源的JS框架jQuery&#xff0c;然后ASP.NET MVC beta发布后&#xff0c;你建立一个ASP.NET MVC beta的项目后&…

Nacos 使用

环境准备 64 bit OS&#xff0c;支持 Linux/Unix/Mac/Windows&#xff0c;推荐选用 Linux/Unix/Mac。64 bit JDK 1.8&#xff1b;下载 & 配置。Maven 3.2.x&#xff1b;下载 & 配置。 下载 Nacos 并启动 Nacos server。 启动配置管理 启动了 Nacos server 后&#x…

四种并发编程模型简介

概述 并发往往和并行一起被提及&#xff0c;但是我们应该明确的是“并发”不等同于“并行” • 并发 &#xff1a;同一时间 对待 多件事情 &#xff08;逻辑层面&#xff09; • 并行 &#xff1a;同一时间 做(执行) 多件事情 (物理层面) 并发可以构造出一种问题解…

从编译到执行,C++如何开发SIMD友好的代码?

一&#xff1a;名词解释 Flynn分类法 Flynn于1972年提出了计算平台的Flynn分类法,主要根据指令流和数据流来分类。按照Flynn分类法&#xff0c;计算平台共分为四种类型。 1.单指令流单数据流机器(SISD) 2.单指令流多数据流机器(SIMD) 3.多指令流单数据流机器(MISD) 4.多指令流…

Nacos介绍

Nacos 英文全称为 Dynamic Naming and Configuration Service&#xff0c;是一个由阿里巴巴团队使用 Java 语言开发的开源项目。 参考:home (nacos.io) Nacos 是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台&#xff0c;可以将 Nacos 理解成服务注册中心…

在部署 C#项目时转换 App.config 配置文件

问题 部署项目时&#xff0c;常常需要根据不同的环境使用不同的配置文件。例如&#xff0c;在部署网站时可能希望禁用调试选项&#xff0c;并更改连接字符串以使其指向不同的数据库。在创建 Web 项目时&#xff0c;Visual Studio 自动生成了 Web.config、Web.Debug.config、We…

设计模式之Factory

设计模式之Factory 2016-08-04 11:57 设计模式总共有23种模式这种都仅仅是为了一个目的&#xff1a;解耦解耦解耦...&#xff08;高内聚低耦合满足开闭原则&#xff09; 介绍: Factory Pattern有3种当然是全部是creational pattern。 1.Simple Factory Pattern 2.Factory…

C#多线程之旅(七)——终止线程

阅读目录 一、什么时候用Thread.Abort();二、Thread.Abort的用法三、无法终止线程的情形四、Catch块中抛出异常五、Finally块中抛出异常六、Abort调用的时间先交代下背景&#xff0c;写《C#多线程之旅》这个系列文章主要是因为以下几个原因&#xff1a;1.多线程在C/S和B/S架构中…

ASP.NET MVC 入门7、Hellper与数据的提交与绑定

本系列文章基于ASP.NET MVC Preview5. ASP.NET MVC提供了很多Hellper的方法&#xff0c;Hellper就是一些生成HTML代码的方法&#xff0c;方便我们书写HTML代码(有一部分的朋友更喜欢直接写HTML代码)。我们也可以利用.NET 3.5的扩展方法来书写我们自己的Hellper。 例如&#x…

ASP.NET MVC 入门8、ModelState与数据验证

ViewData有一个ModelState的属性&#xff0c;这是一个类型为ModelStateDictionary的ModelState类型的字典集合。在进行数据验证的时候这个属性是比较有用的。在使用Html.ValidationMessage()的时候&#xff0c;就是从ViewData.ModelState中检测是否有指定的KEY&#xff0c;如果…

ASP.NET MVC 入门9、Action Filter 与 内置的Filter实现(介绍)

本系列文章基于ASP.NET MVC Preview5. 有时候你想在调用action方法之前或者action方法之后处理一些逻辑&#xff0c;为了支持这个&#xff0c;ASP.NET MVC允许你创建action过滤器。Action过滤器是自定义的Attributes&#xff0c;用来标记添加Action方法之前或者Action方法之后…

ASP.NET MVC 入门10、Action Filter 与 内置的Filter实现(实例-防盗链)

本系列文章基于ASP.NET MVC Preview5. 前一篇中我们已经了解了Action Filter 与 内置的Filter实现&#xff0c;现在我们就来写一个实例。就写一个防盗链的Filter吧。 首先继承自FilterAttribute类同时实现IActionFilter接口&#xff0c;代码如下&#xff1a; /// <summary…

base64原理及其编解码的python实现

base64原理及其编解码的python实现base64base64简介base64编码表base64编码原理base64编解码的python实现其他base编码base16base32base36、base58、 base62、 base85、base91、 base92base64 base64简介 base64是一种基于64个可打印字符来表示二进制数据的表示方法。2664&am…

REVERSE-PRACTICE-JarvisOJ-1

REVERSE-PRACTICE-JarvisOJ-1[61dctf]androideasy[61dctf]stheasyDD - Android NormalDD - Android Easy[61dctf]androideasy apk文件&#xff0c;用jadx-gui打开 主要的逻辑为&#xff0c;获取输入&#xff0c;检验输入的长度&#xff0c;输入异或23后与已知数组比较&#xf…

NuGet学习笔记(1) 初识NuGet及快速安装使用

关于NuGet园子里已经有不少介绍及使用经验&#xff0c;本文仅作为自己研究学习NuGet一个记录。 初次认识NuGet是在去年把项目升级为MVC3的时候&#xff0c;当时看到工具菜单多一项Library Package Manager&#xff0c;右键项目文件多了一项Manage Nuget Packages...&#xff0c…

NuGet学习笔记(2) 使用图形化界面打包自己的类库

上文NuGet学习笔记(1) 初识NuGet及快速安装使用说到NuGet相对于我们最重要的功能是能够搭建自己的NuGet服务器&#xff0c;实现公司内部类库的轻松共享更新。在安装好NuGet扩展后&#xff0c;我们已经能够通过NuGet轻松下载自己需要的类库&#xff0c;下面来说一说如何将自己的…

REVERSE-PRACTICE-JarvisOJ-2

REVERSE-PRACTICE-JarvisOJ-2DD - HelloAPK_500DebugMeFindPassDD - Hello macos文件&#xff0c;无壳&#xff0c;ida分析 start函数和sub_100000C90函数没什么作用 主要的逻辑在sub_100000CE0函数&#xff0c;反调试检测和byte_100001040数组的循环变换&#xff0c;最后打印…