在C#中,系统提供了GUID类,用户可以通过该类来获得128位的唯一标识,但是该标识不具有可读性,很难把该GUID显示在界面上,以当前时间精确到毫秒来作为GUID是一个比较不错的做法,但是DateTime.now的误差是100ms左右,无法在毫秒级的并发情况下获得不同的时间。
在项目中,为了在界面上面显示时间相关的单号,是使用Datetime.now.ToString("yyyyMMddHHmmssfff"),结果在毫秒级多次调用该函数的时候,发现获得的字符串居然是一样的。在晚上查了一下,DateTime.now是有误差的,误差是100ms左右。没有很好的获得毫秒级时间的方法,我的想法是在该字符串后面加上个标识来使该字符串变得“唯一”。每次调用该方法时,该标识+1。去掉年份的4位,在后面加上该标识,从而保持获得的id依旧是17位。当该标识==9999时,将其重置为0。这样就可以获得一个唯一的时间相关的字符串了。类似这样:
datetime.now.ToString()+(flag++) .ToString(); if (flag >= 9999) flag = 0;
在并发的情况下,需要使+(flag++)原子化,如下:
static int flag = 0; static object o = new object(); public static string GetTimeGuid(){ int pFlag = 0; Monitor.Enter(o); if (flag++>=9999) flag = 0; pFlag = flag; Monitor.Exit(o); return DateTime.Now.ToString("MMddHHmmssfff")+pFlag.ToString(); }
值得注意的是,返回的字符串中是访问的局部变量pFlag而不是flag,因为此时已经不在Monitor里面了,无法保证flag是否只执行过一次++操作。我没有在此处使用interlock中的方法,是因为无法做到既flag++且读取flag的值并判断flag是否大于9999这三步操作的整体的原子性,只能使用锁。开始我担心锁会造成过多的性能损耗,因此对该方法进行了测试,
static void Main(string[] args) { var array = new string[100]; var taskArray = new Task[100]; var stopwatch = new Stopwatch(); for (var i = 0; i < 100; i++) { var p = i; Action ac = () => array[p] = GetTimeGuid(); taskArray[p] = new Task(ac); } stopwatch.Start(); foreach (var t in taskArray) { t.Start(); } var factory = new TaskFactory(); factory.ContinueWhenAll(taskArray, t => { stopwatch.Stop(); long time1, time2; time1 = stopwatch.ElapsedMilliseconds; foreach (var item in array) { Console.WriteLine(item); } for (var i = 0; i < 100; i++) { var p = i; Action ac = () => array[p] = DateTime.Now.ToString("yyyyMMddHHmmssfff"); taskArray[p] = new Task(ac); } stopwatch = new Stopwatch(); stopwatch.Start(); foreach (var task in taskArray) { task.Start(); } factory.ContinueWhenAll(taskArray, n => { stopwatch.Stop(); time2 = stopwatch.ElapsedMilliseconds; foreach (var item in array) { Console.WriteLine(item); } Console.WriteLine($"{time1},{time2}"); }); }); Console.Read(); }
这段代码我在我的老旧笔记本上面和台式机上面跑,结果相差非常大,台式机上面只差5ms以内,但是在笔记本上面要差10倍以上。
本文介绍了一种将时间作为GUID的方法,欢迎在评论区和我讨论。