集合类的线程安全

时间:2022-09-03 18:36:00

在.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();
}
}