关于Parallel我也不细说了,一则微软封装的很好用,二来介绍这个的遍地都是。
我要说的是,要想成为一个优秀的标题党,一定要把重点放到别的地方,为了节省大家阅读时间,我先把结论说了,然后再慢慢从头说,愿意往下看的可以看看因果,不愿意看的,也算咱给大家提个醒吧。
结论就是,大家在做单元测试用例的时候一定要用心,这篇随笔是要检讨并警醒下自己并与大家共勉。
据一些我不记得名字的国外权威专家,据说是测试大师级人物研究,测试用例的出现的错误并不比被测试的代码少,我相信大多数人会和我类似,在写代码的时候小心翼翼,但是为代码编写单元测试及用例的时候往往注意力下意识的不那么集中,今天就是这么个情况。可能是缺乏锻炼(我天天打太极拳的。。。)的原因,连续几个晚上由于学习原因睡的比较晚,今天就比较困,然后干了件点题的事。
我写了一个对象ID生成器,以时间戳和顺序号组合,虽然不支持集群,但是目前情况下足够了,然后因为复制数据库的同事坚决不同意将主键的int类型改掉,那怕改成bigint都不同意。。。跑题了。总之,各种限制的情况下出了这么个东西:
//不能再集群情况下使用 public class IDGenerator {
private static double _seconds = 0; private static int _sequenceIndex = 0; private static object _lock = new object();
private static ReaderWriterLockSlim writerLock = new ReaderWriterLockSlim(); private static IDGenerator _instance; private IDGenerator() { } public static int NewID { get { int newID = IDGenerator.Instance.CreateNewID(); return newID; } } private int CreateNewID() { string id; writerLock.EnterReadLock(); try { id = BuildIDAndUpdateSead(); } finally//保证退出锁 { writerLock.ExitReadLock(); } int newID = TypeParseHelper.GetIntValueByString(id); return newID; } private string BuildIDAndUpdateSead() { double seconds; bool isEqualSecondsCount; seconds = InterceptSecondsInTicks(); isEqualSecondsCount = seconds.Equals(_seconds); if (isEqualSecondsCount) {
bool isUpperBound = _sequenceIndex == 999; if (isUpperBound) { Thread.Sleep(1000); _sequenceIndex = 0; _seconds++; }
else
{
Interlocked.Increment(ref _sequenceIndex);
}
}
else
{ _seconds = seconds; } string id = _seconds.ToString() + _sequenceIndex.ToString().PadLeft(3, '0'); return id; } private double InterceptSecondsInTicks() { DateTime currentDate = DateTime.Now; long elapsedTicks = (currentDate.Ticks - ConstantGather.BEGIN_STANDARD_TICKS); TimeSpan elapsedSpan = new TimeSpan(elapsedTicks); double seconds = Math.Floor(elapsedSpan.TotalSeconds); return seconds; } private static IDGenerator Instance { get { if (_instance != null) { return _instance; } lock (_lock) { if (_instance == null) { _instance = new IDGenerator(); } } return _instance; } } }
做完了,当然要写点单元测试测试一下,就先写了个同步的,然后写了个异步的,然后写了个并发的
public List<int> GenerateRequisitionIDs() { TaskFactory taskFactory = new TaskFactory(); Task<int>[] tasks = new Task<int>[] { taskFactory.StartNew(() => GenerateRequisitionID()), taskFactory.StartNew(() => GenerateRequisitionID()), taskFactory.StartNew(() => GenerateRequisitionID()) }; var query = tasks.Where(t => !t.Result.Equals(0)) .Select(t => t.Result); return query.ToList(); }
高潮来了,然后我在单元测试里顺手写了个并行的。。。
Parallel.For(0, actual, c => { int id = target.GenerateRequisitionID(); list.AddLast(id); });
测试结果就不停得出错,我费了很大功夫定位到了出错的位置。。。
string idStr = _seconds.ToString() + "-" + _sequenceIndex.ToString().PadLeft(3, '0');
这一句是我写了一堆定位错误的代码中的一句,发现了个奇怪的现象,这个字符串赋值后,两边居然是不一样的,而且这一句在排它锁里。。。
并行的用法应该是将一对任务,平均分开,各自做各自的,互相不影响,问题就在于我这生成器是个单例。。。
作为一个敬业的标题党,并发和并行的区别我就不说了,反正连百度都知道的事。。。
最后说一句,单元测试要认真,单元测试的代码哪怕随便玩的也要组织好,不要乱放,不要完全不着边的都测,正常业务里是不会有并行生成ID的场景的,这个测试没有意义,而且,如果看了生成器的代码会发现,由于int32本身的长度问题,我限定了一秒最多能生成999个ID,我需要测的其实不是并行问题,太晚了,思维比较跳跃,就这么着吧,下次长点记性就好了,以上。