c# - 在英特尔 NUC NUC7CJYH 上使用 ~100% CPU 使用率的 WPF QR 解码应用程序的优化
问题描述
我一直在研究一个 WPF,它解码用户持有网络摄像头的 QR 码。该应用程序运行良好,在我的开发机器上运行良好(Core i7 3770 CPU,NVidia Quadro K4200 GPU 上最多 23% 的 CPU 和 4% 的 GPU 使用率)但是当我在机器上安装并运行它时,它将被使用(英特尔 NUC NUC7CJYH)应用程序的 CPU 使用率 > 94%,导致机器上的使用率为 100%。
目前,该应用程序会扫描用户的二维码两次——第一次是员工的二维码,第二次是他们从事的项目编号的二维码。该应用程序使用 ZXing.Net 解码 QR 码,并使用 AForge.Net 访问网络摄像头。
我已经运行了 VS Profiler,这是结果的屏幕截图: VS Profiler Output
根据 VS Profiler 的结果,有 4 个方法调用使用了最多的 CPU 时间,大概我应该关注:
videoSource_NewFrame:获取在 VideoCaptureDevice(AForge.Video.DirectShow 类的一部分)中呈现的每一帧,并将其显示在名为 imgSource 的 Image 控件中。这使用了约 10.56% 的总 CPU 时间(3745 毫秒)
void videoSource_NewFrame(object sender, NewFrameEventArgs eventArgs) { try { BitmapImage bi; using (var bitmap = (Bitmap)eventArgs.Frame.Clone()) { bi = new BitmapImage(); bi.BeginInit(); MemoryStream ms = new MemoryStream(); bitmap.Save(ms, ImageFormat.Bmp); ms.Seek(0, SeekOrigin.Begin); bi.StreamSource = ms; bi.CacheOption = BitmapCacheOption.OnLoad; bi.EndInit(); } bi.Freeze(); Dispatcher.BeginInvoke(new ThreadStart(delegate { imgSource.Source = bi; })); } catch (Exception ex) { MessageBox.Show("Error with attaching video frame.\n " + ex.Message); MessageBox.Show("An error occurred. \nPlease contact the Systems Development team for assistance.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; } }
timer_Tick:该方法是一个DispatcherTimer Tick EventHandler,每隔1秒调用一次。这用于在 lblTime 控件中显示当前时间,它使用了约 9.56% 的 CPU 时间(3389 毫秒)
private void timer_Tick(object sender, EventArgs e) { currentTime = new DateTime(); ts = new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second); currentTime = currentTime.Date + ts; lblTime.Content = $"{currentTime.ToString("HH:mm")}"; }
qrTimer_Tick:另一个 DispatcherTimer Tick EventHandler。此方法每 3 秒调用一次,并在每个间隔开始扫描 QR 码。它正在使用 ~5.52% (1959ms)
try { MemoryStream memoryStream = new MemoryStream(); var encoder = new System.Windows.Media.Imaging.BmpBitmapEncoder(); encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(imgSource.Source as System.Windows.Media.Imaging.BitmapSource)); encoder.Save(memoryStream); memoryStream.Flush(); capturedImage = (Bitmap)System.Drawing.Image.FromStream(memoryStream); BarcodeReader reader = new BarcodeReader(); reader.AutoRotate = true; reader.TryInverted = true; reader.Options = new DecodingOptions { TryHarder = true }; if (capturedImage != null) result = reader.Decode(capturedImage); if (result != null) { if (isStep1 == true) { QRScanUserID(); } else if (isStep1 == false & isStep2 == true) { QRScanProjectID(); } } } catch (Exception) { MessageBox.Show("An error occurred. \nPlease contact the Systems Development team for assistance.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); return; }
对 ZXing.Net 的 Decode 方法的外部调用。这使用了总 CPU 时间的 5.34%(1895 毫秒)
我不确定如何优化代码。我已经查看了为两个 DispatcherTimer 中的每一个设置 DispactherPriority,但在测试中这对应用程序的 CPU 使用率没有影响,并且会干扰每个 DispatcherTimer 的间隔。我还尝试将我在 qrTimer_Tick 方法中配置的 3 个 BarcodeReader 选项更改为 false,这产生了大约 1% 的改进。但是,我认为这可以忽略不计,因为它取决于用户将 QR 码放在相机前的速度以及 qrTimer_Tick 是否已经触发等因素。
有什么我想念的吗?难道仅仅是该应用程序压倒了它运行的 NUC 机器吗?
编辑 在遵循 kennyzx 和 lerthe61 的建议后,我设法将 NUC NUC7CJYH 上的应用程序 CPU 使用率降低到峰值时的约 58%。最大的好处是删除了每秒调用 timer_Tick 事件处理程序的 DispatcherTimer 对象。在我进行此更改后不久运行 VS Profiler 显示我的开发机器上的峰值 CPU 使用率为 18%,这是在应用程序启动后不久,必须自行运行。
在 lerthe61 的输入之后,我查看了我对 MemoryStream 和 Bitmap 对象的使用。videoSource_NewFrame 方法现在如下所示:
void videoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
try
{
BitmapImage bi;
capturedImage = (Bitmap)eventArgs.Frame.Clone();
bi = new BitmapImage();
bi.BeginInit();
ms = new MemoryStream();
capturedImage.Save(ms, ImageFormat.Bmp);
ms.Seek(0, SeekOrigin.Begin);
bi.StreamSource = ms;
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.EndInit();
bi.Freeze();
Dispatcher.BeginInvoke(new ThreadStart(delegate { imgSource.Source = bi; }));
}
catch (Exception ex)
{
MessageBox.Show("Error with attaching video frame.\n " + ex.Message);
MessageBox.Show("An error occurred. \nPlease contact the Systems Development team for assistance.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
}
我现在在 qrTimer_Tick 方法中使用了 lock 来锁定来自网络摄像头的当前捕获的图像:
if (capturedImage != null)
{
lock (_qrTimerLock)
{
result = reader.Decode(capturedImage);
}
}
尽管我使用了锁,但该应用程序偶尔会抛出错误“对象当前正在其他地方使用”。我对代码的重构在删除 timer_Tick 之上对应用程序的 CPU 使用效率影响不大,所以我可以保持原样,但这显然效率较低。
解决方案
我决定移除每秒运行的 DispatcherTimer,留下每 3 秒运行的第二个 DispatcherTimer 对象。仅使用此修复程序的应用程序的性能是有利的。
感谢大家的帮助!
推荐阅读
- ios - 实施核心数据的问题
- javascript - 我的答题器游戏正在保存,但是当我尝试加载时,它会将所有内容显示为起始编号
- java - Java(Maven)中深层文件夹结构的意义何在?
- spring - Spring Boot 和 Spring Security 中的入职过滤器
- python - 按绝对值之和的顺序迭代对
- visual-studio - 有没有办法防止变更集评论更新?
- javascript - 为什么不会
行为正确 - mongodb - 在客户端暂停/恢复 MongoDB 领域同步的解决方法或方法?
- sharepoint-online - 如何在 SPFx 中查找列类型?
- css - flex 换行时的边距