集合类的线程安全

在.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();
            }
        }
       
原文地址:https://www.cnblogs.com/cglNet/p/6370813.html