在.Net Framework 4.0之前,为解决集合的安全问题,主要用到的方法是锁。举一个例子。假若计算机的某一个文件夹中存在1000张图片,现在要对这1000张图片做缩略图处理,把处理好的缩略图保存到另外一个文件夹中。
先写一个处理图片缩略图的类,代码图下所示:
public class OperPicClass { /// <summary> /// 输入jpg文件 /// </summary> public string InFileName { get; set; } /// <summary> /// 输出jpg缩略图的文件 /// </summary> public string OutFileName { get; set; } public OperPicClass() { } /// <summary> /// 处理缩略图 /// </summary> /// <returns></returns> public bool Exc() { //string pDir = System.IO.Path.GetDirectoryName(OutFileName); //if (!System.IO.Directory.Exists(pDir)) //{ // System.IO.Directory.CreateDirectory(pDir); //} Console.WriteLine("处理图片:" + Thread.CurrentThread.Name + "||" + InFileName); return true; } }
单线程情况下,对1000张图片的处理如下:
private void btRun_Click(object sender, EventArgs e) { //单线程下,图片生成缩略图 List<OperPicClass> listJpgFiles = new List<OperPicClass>(); for (int i = 0; i < 1000; i++) { listJpgFiles.Add(new OperPicClass() { InFileName = "文件路径", OutFileName = "输出路径" }); } listJpgFiles.ForEach((o) => { o.Exc(); }); }
单线程下,会导致1000张图片处理时间长。分析1000张图片做缩略图,整个过程互不干扰,考虑用多线程的方式,减少处理时间。在.Net Framework 4.0出现之前,为了保证集合安全,需要重写一个安全的集合类,如下所示:
public class MutilThreadOperClass<T> : List<T> where T : new() { private readonly object _lock; public MutilThreadOperClass() { _lock = new object(); } public new void Add(T t) { lock (_lock) { base.Add(t); } } private int _index = 0; public T getObject() { T t = default(T); if (_index >= 0 && _index < this.Count) { lock (_lock) { t = this[_index]; _index++; } } return t; } }
集合类写好后,多线程处理如下所示:
private void btRun_Click(object sender, EventArgs e) { MutilThreadOperClass<OperPicClass> tMutilThreadOperClass = new MutilThreadOperClass<OperPicClass>(); for (int i = 0; i < 10000; i++) { tMutilThreadOperClass.Add(new OperPicClass() { InFileName = "文件路径" + i, OutFileName = "输出路径" }); } Thread[] tThread = new Thread[8]; for (int i = 0; i < tThread.Length; i++) { tThread[i] = new Thread(() => { while (true) { Thread.Sleep(10); try { OperPicClass d = tMutilThreadOperClass.getObject(); d.Exc(); } catch { break; } } }); tThread[i].IsBackground = true; tThread[i].Name = "我是线程" + i; tThread[i].Start(); } }
.Net Framework 4.0出现之后,微软提供了System.Collections.Concurrent命名空间,在此命名空间中,有以下类:
类说明 | ||
---|---|---|
BlockingCollection<T> | 为实现 IProducerConsumerCollection<T> 的线程安全集合提供阻塞和限制功能。 | |
ConcurrentBag<T> | 表示对象的线程安全的无序集合。 | |
ConcurrentDictionary<TKey, TValue> | 表示可由多个线程同时访问的键值对的线程安全集合。 | |
ConcurrentQueue<T> | 表示线程安全的先进先出 (FIFO) 集合。 | |
ConcurrentStack<T> | 表示线程安全的后进先出 (LIFO) 集合。 | |
OrderablePartitioner<TSource> | 表示将一个可排序数据源拆分成多个分区的特定方式。 | |
Partitioner | 提供针对数组、列表和可枚举项的常见分区策略。 | |
Partitioner<TSource> | 表示将一个数据源拆分成多个分区的特定方式。 |
其中,经常用到是ConcurrentBag<T>、ConcurrentQueue<T>、ConcurrentDictionary<TKey, TValue>。以ConcurrentBag<T>为例。实现上面的过程。首先用它的优点就是不需要再额外写MutilThreadOperClass这个类了。也可以理解为微软用ConcurrentBag<T>代替了自己写的这个安全类。其次就是线程安全,可以自由访问集合中的要素了。代码如下:
private void btRun_Click(object sender, EventArgs e) { System.Collections.Concurrent.ConcurrentBag<OperPicClass> tConcurrentBag = new System.Collections.Concurrent.ConcurrentBag<OperPicClass>(); for (int i = 0; i < 10000; i++) { tConcurrentBag.Add(new OperPicClass() { InFileName = "文件路径" + i, OutFileName = "输出路径" }); } Thread[] tThread = new Thread[8]; for (int i = 0; i < tThread.Length; i++) { tThread[i] = new Thread(() => { OperPicClass d; while (tConcurrentBag.TryTake(out d))//TryTake方法与TryPeek方法的区别在于是否移除元素。 { Thread.Sleep(10); d.Exc(); } }); tThread[i].IsBackground = true; tThread[i].Name = "我是线程" + i; tThread[i].Start(); } }