我在winform项目里使用“Windows I/O完成端口”的经验分享
少年!看你骨骼惊奇,是万中无一的练武奇才,我这儿有本武林秘籍,见与你有缘就送你了!
如来神掌
Windows I/O完成端口是一个我至今都说不好的话题,请宽容的接受我这不是科班出身的自学成才的野生程序员身份。以前在上海一公司做产品追溯的时候,我的老大拿出一本《Windows核心编程》经常向我吹嘘什么“ Windows I/O完成端口”编程模型的时候我是云里雾里。后来看了公司常用的一个叫“线程池”的类的源码,豁然有点醒悟了,不就是类似Queue这样的东西么?按先进先出顺序处理业务数据,这明明就不是线程池啊,误导人了。但是这个类确实挺好用的,公司它都使用了很多年了。不想独享特此分享出来。
public class CoreThreadPool : IDisposable { /// <summary> /// 队列元素申明 /// </summary> [StructLayout(LayoutKind.Sequential)] private class PoolData { /// <summary> /// 外部要求放入队列的数据 /// </summary> public object Data; /// <summary> /// 需要执行的命令(Exit/Command(自定义)) /// </summary> public PoolCommand Command; public PoolData() { Command = PoolCommand.Exit; } public PoolData(object data) { Data = data; Command = PoolCommand.Command; } public PoolData(PoolCommand cmd) { Command = cmd; } } protected enum PoolCommand { Command, Exit } protected SafeFileHandle complatePort; /// <summary> /// 线程池主线程 /// </summary> protected Thread thread; protected volatile bool isOpened; [method: CompilerGenerated] [CompilerGenerated] public event Action<object> Exceute; [method: CompilerGenerated] [CompilerGenerated] public event Action<object> ExitExceute; /// <summary> /// 线程池是否正在运行 /// </summary> public bool IsOpened { get { return this.isOpened; } set { this.isOpened = value; } } [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern SafeFileHandle CreateIoCompletionPort(IntPtr FileHandle, IntPtr ExistingCompletionPort, IntPtr CompletionKey, uint NumberOfConcurrentThreads); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool GetQueuedCompletionStatus(SafeFileHandle CompletionPort, out uint lpNumberOfBytesTransferred, out IntPtr lpCompletionKey, out IntPtr lpOverlapped, uint dwMilliseconds); [DllImport("Kernel32", CharSet = CharSet.Auto)] private static extern bool PostQueuedCompletionStatus(SafeFileHandle CompletionPort, uint dwNumberOfBytesTransferred, IntPtr dwCompletionKey, IntPtr lpOverlapped); /// <summary> /// 启动线程池的主线程 /// </summary> public void Start() { isOpened = true; if (thread != null) { throw new Exception("线程池已经是启动状态!"); } complatePort = CreateIoCompletionPort(new IntPtr(-1), IntPtr.Zero, IntPtr.Zero, 0u); if (complatePort.IsInvalid) { throw new Exception(string.Format("创建IOCP出错!原因是:{0}", Marshal.GetLastWin32Error().ToString())); } thread = new Thread(new ParameterizedThreadStart(this.Run)); thread.Start(complatePort); } /// <summary> /// 外部提交数据对象到队列 /// </summary> /// <param name="data"></param> public void Post(object data) { PostData(new PoolData(data)); } /// <summary> /// 线程池主线程执行逻辑 /// </summary> /// <param name="CompletionPortID"></param> private void Run(object CompletionPortID) { SafeFileHandle completionPort = (SafeFileHandle)CompletionPortID; while (IsOpened) { uint num; IntPtr intPtr; IntPtr value; //从队列里取出最前面的对象 GetQueuedCompletionStatus(completionPort, out num, out intPtr, out value, 4294967295u); if (num > 0u) { GCHandle gCHandle = GCHandle.FromIntPtr(value); PoolData poolData = (PoolData)gCHandle.Target; gCHandle.Free(); if (poolData.Command != PoolCommand.Command) { IsOpened = false; break; } RaiseExecute(poolData.Data); } } RaiseExitExecute("线程池已经停止。"); isOpened = false; thread = null; } /// <summary> /// 触发Execute事件 /// </summary> /// <param name="data"></param> private void RaiseExecute(object data) { Exceute?.Invoke(data); } /// <summary> /// 触发ExitExecute事件 /// </summary> /// <param name="data"></param> private void RaiseExitExecute(object data) { ExitExceute?.Invoke(data); } /// <summary> /// 结束线程池主线程 /// </summary> public void Stop() { PostData(new PoolData(PoolCommand.Exit)); IsOpened = false; } /// <summary> /// 内部提交数据到线程池队列中 /// </summary> /// <param name="data"></param> private void PostData(PoolData data) { if (complatePort.IsClosed) { return; } GCHandle value = GCHandle.Alloc(data); PostQueuedCompletionStatus(complatePort, (uint)IntPtr.Size, IntPtr.Zero, GCHandle.ToIntPtr(value)); } public void Dispose() { if (thread != null && thread.ThreadState != ThreadState.Stopped) { Stop(); } } }
第1001次实践体验过程
上次做的人脸考勤程序在处理多个人同时考勤时我就使用了刚刚的类。
private CoreThreadPool pool = newCoreThreadPool();private CoreThreadPool poolExt = newCoreThreadPool();
...
pool.Exceute+=Pool_Exceute;
pool.Start();
poolExt.Exceute+=PoolExt_Exceute;
poolExt.Start()
private void Pool_Exceute(objectobj)
{var entity = obj asUserInfo;if (entity == null) return;try{#region TODO本地防止重复请求 using (DefaultDbContext db = newDefaultDbContext())
{var dbEntity = db.Attenducelog.Where(e => e.Emp_No ==entity.EmpNo).First();
DateTime dt;if (dbEntity == null)
{//第一次考勤 dbEntity = newAttenducelog_Entity();
dbEntity.Emp_No=entity.EmpNo;
dt= DateTime.Now.AddDays(-1);
dbEntity.Log_DateTime=dt;
db.Attenducelog.Add(dbEntity);
db.SaveChanges();
}else{//已经多次考勤 dt =dbEntity.Log_DateTime;
}
TimeSpan ts= DateTime.Now -dt;if (ts.TotalSeconds < 61)
{return;
}else{//已经多次考勤,本次成功了才记录打卡时间 dbEntity = db.Attenducelog.Where(e => e.Emp_No ==entity.EmpNo).First();
dbEntity.Log_DateTime=DateTime.Now;
db.Attenducelog.Update(dbEntity);
db.SaveChanges();
}
}#endregion string url = $"{config.AppSettings.Settings["Platform"].Value}/business/attendancedetails/AddAttendanceDetails";#region dtoPlatAttendanceDto dto= newPlatAttendanceDto();
dto.KeyId=Guid.NewGuid().ToString();
dto.Status= 0;
dto.AuditDate= DateTime.Now.ToString("yyyy-MM-dd");
dto.CreateBy= "AttendanceClient";
dto.AttendanceDatetime= DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
dto.FkStore= config.AppSettings.Settings["StoreID"].Value;
dto.EmpName=entity.Name;
dto.EmpNo=entity.EmpNo;
dto.WorkShift= "";
dto.LocalDatetime=DateTime.Now;#endregion string jsonData =JsonConvert.SerializeObject(dto);string rs =Program.PostJsonData(url, jsonData);if (!string.IsNullOrEmpty(rs) && JObject.Parse(rs).Value<int>("code").Equals(200))
{
JObject rs_Object=JObject.Parse(rs);string data = rs_Object["data"].ToString();
JObject log=JObject.Parse(data);string sound_TIPS = log.Value<string>("remark").Split("&".ToCharArray()).LastOrDefault();string tips = "[" + entity.Name + "]" + log.Value<string>("remark").Split("&".ToCharArray()).LastOrDefault();
AppSpVoiceSpeak(sound_TIPS);
MessageTip.ShowOk(tips,3000);
}
}catch(Exception ex)
{if (ex.Message.Contains("无法连接到远程服务器"))
{
Thread.Sleep(100);
ViewFaceCore.Controls.MessageTip.ShowError("无法连接到远程服务器" + Environment.NewLine + "Unable to connect to remote server", 300);
}
}finally{
Thread.Sleep(100);
}
}
/// <summary> ///持续检测一次人脸,直到停止。/// </summary> /// <param name="token">取消标记</param> private async voidStartDetector(CancellationToken token)
{
List<double> fpsList = new List<double>();double fps = 0;
Stopwatch stopwatchFPS= newStopwatch();
Stopwatch stopwatch= newStopwatch();
isDetecting= true;try{if (VideoPlayer == null)
{return;
}if (token == null)
{return;
}while (VideoPlayer.IsRunning && !token.IsCancellationRequested)
{try{if(CheckBoxFPS.Checked)
{
stopwatch.Restart();if (!stopwatchFPS.IsRunning)
{ stopwatchFPS.Start(); }
}
Bitmap bitmap= VideoPlayer.GetCurrentVideoFrame(); //获取摄像头画面 if (bitmap == null)
{await Task.Delay(10, token);
FormHelper.SetPictureBoxImage(FacePictureBox, bitmap);continue;
}if (!CheckBoxDetect.Checked)
{await Task.Delay(1000 / 60, token);
FormHelper.SetPictureBoxImage(FacePictureBox, bitmap);continue;
}
List<Models.FaceInfo> faceInfos = new List<Models.FaceInfo>();using (FaceImage faceImage =bitmap.ToFaceImage())
{var infos = await faceFactory.Get<FaceTracker>().TrackAsync(faceImage);for (int i = 0; i < infos.Length; i++)
{
Models.FaceInfo faceInfo= newModels.FaceInfo
{
Pid=infos[i].Pid,
Location=infos[i].Location
};if (CheckBoxFaceMask.Checked ||CheckBoxFaceProperty.Checked)
{
Model.FaceInfo info=infos[i].ToFaceInfo();if(CheckBoxFaceMask.Checked)
{var maskStatus = await faceFactory.Get<MaskDetector>().PlotMaskAsync(faceImage, info);
faceInfo.HasMask=maskStatus.Masked;
}if(CheckBoxFaceProperty.Checked)
{
FaceRecognizer faceRecognizer= null;if(faceInfo.HasMask)
{
faceRecognizer=faceFactory.GetFaceRecognizerWithMask();
}else{
faceRecognizer= faceFactory.Get<FaceRecognizer>();
}var points = await faceFactory.Get<FaceLandmarker>().MarkAsync(faceImage, info);float[] extractData = awaitfaceRecognizer.ExtractAsync(faceImage, points);
UserInfo userInfo=CacheManager.Instance.Get(faceRecognizer, extractData);if (userInfo != null)
{
faceInfo.Name=userInfo.Name;
faceInfo.Age=userInfo.Age;switch(userInfo.Gender)
{caseGenderEnum.Male:
faceInfo.Gender=Gender.Male;break;caseGenderEnum.Female:
faceInfo.Gender=Gender.Female;break;caseGenderEnum.Unknown:
faceInfo.Gender=Gender.Unknown;break;
}
pool.Post(userInfo);
}else{
faceInfo.Age= await faceFactory.Get<AgePredictor>().PredictAgeAsync(faceImage, points);
faceInfo.Gender= await faceFactory.Get<GenderPredictor>().PredictGenderAsync(faceImage, points);
}
}
}
faceInfos.Add(faceInfo);
}
}using (Graphics g =Graphics.FromImage(bitmap))
{#region 绘制当前时间StringFormat format= newStringFormat();
format.Alignment=StringAlignment.Center;
format.LineAlignment=StringAlignment.Center;
g.DrawString($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", new Font("微软雅黑", 32), Brushes.White, new Rectangle(0, 0, Width - 32, 188), format);
g.DrawString($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", new Font("微软雅黑", 32), Brushes.White, new Rectangle(2, 2, Width - 32, 188), format);#endregion //如果有人脸,在 bitmap 上绘制出人脸的位置信息 if(faceInfos.Any())
{
g.DrawRectangles(new Pen(Color.Red, 4), faceInfos.Select(p =>p.Rectangle).ToArray());if(CheckBoxDetect.Checked)
{for (int i = 0; i < faceInfos.Count; i++)
{
StringBuilder builder= newStringBuilder();if(CheckBoxFaceProperty.Checked)
{if (!string.IsNullOrEmpty(faceInfos[i].Name))
{
builder.Append(faceInfos[i].Name);
}
}if (builder.Length > 0)
{
g.DrawString(builder.ToString(),new Font("微软雅黑", 32), Brushes.White, new PointF(faceInfos[i].Location.X + faceInfos[i].Location.Width + 24, faceInfos[i].Location.Y));
g.DrawString(builder.ToString(),new Font("微软雅黑", 32), Brushes.White, new PointF(faceInfos[i].Location.X + faceInfos[i].Location.Width + 24 + 2, faceInfos[i].Location.Y + 2));
}
}
}
}if(CheckBoxFPS.Checked)
{
stopwatch.Stop();if (numericUpDownFPSTime.Value > 0)
{
fpsList.Add(1000f/stopwatch.ElapsedMilliseconds);if (stopwatchFPS.ElapsedMilliseconds >=numericUpDownFPSTime.Value)
{
fps=fpsList.Average();
fpsList.Clear();
stopwatchFPS.Reset();
}
}else{
fps= 1000f /stopwatch.ElapsedMilliseconds;
}
g.DrawString($"{fps:#.#} FPS", new Font("微软雅黑", 24), Brushes.Green, new Point(10, 10));
}
}
FormHelper.SetPictureBoxImage(FacePictureBox, bitmap);
}catch(TaskCanceledException)
{break;
}catch{ }
}
}catch(Exception ex)
{
Program.AppLogger.Error(ex);
}finally{
isDetecting= false;
}
}
其实触发数据就一句代码,看起来像这样:
pool.Post(userInfo);
好了,高手请看笑话吃瓜,有需要的同学可亲自尝试。bye 了个 bye!