【发布时间】:2018-09-06 12:53:52
【问题描述】:
我一直在开发一个 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 方法的外部调用。这使用了 5.34% 的总 CPU 时间(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 使用效率影响不大,所以我可以保持原样,但这显然效率较低。
【问题讨论】:
-
@dumetrulo 感谢您的编辑。发布代码总是让我看到火焰,我不知道为什么:/
-
您的开发机器上的 CPU 有 4 个核心,而 NUC7CJYH 是单核 CPU,这是巧合吗?请检查我的两个假设是否正确。
-
@kennyzx 我相信它是双核的(见link)。但是,它是 Celeron (J4005) 并且它具有板载图形,所以您可能会在这里找到一些东西......没有任何明显的性能下降,但是我不喜欢它被最大化的事实,因为这是应用程序将在其上运行的机器
-
看起来 100% 的 NUC7CJYH CPU 功率被 2 个线程使用(它可以处理 2 个并发线程)。 i7 3770 4 核 CPU 可以处理 8 个线程。这就是为什么你得到大约 25% 的使用率。你能把定时器的滴答间隔加长吗?
-
1.您在每一帧上启动一个新线程只是为了更新图像; 2. 从相机中克隆每个位图的目的是什么? 3. 不清楚 DispatcherTimer 中的代码为何如此复杂。有什么区别;
lblTime.Content = DateTime.Now.ToString("HH:mm")? 4. 这是从 MemoryStream 到 Bitmap 的转换(可能是为了显示在表单上),然后从该位图到 MemoryStream 再到 Bitmap: 清单 1:bitmap.Save(ms, ImageFormat.Bmp)清单 3:encoder.Save(memoryStream)清单 3:capturedImage = (Bitmap)System.Drawing.Image.FromStream(memoryStream)