我对C#和编程还比较陌生,但是我已经做了几个应用程序。通常只做一项任务然后退出的应用程序很简单,但是例如每天拍摄500张用户照片的系统给了我一个更大的挑战。
我的问题与WPF中的内存消耗有关。我有以下页面,加载时会不断消耗内存。我尝试使用内存分析工具并创建了一些快照来解决这个问题。然而,我很难理解何时/如何处理对象以确保GC处理其余部分。我特别遇到麻烦的其中一页是这一页:
第二页:
using EDSDKLib;
using PhotoBooth.Functions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace PhotoBooth.Pages
{
/// <summary>
/// Interaction logic for Picture.xaml
/// </summary>
public partial class Picture : Page
{
int secondsToWait = 4;
DispatcherTimer dispatcherTimer;
Action<BitmapImage> SetImageAction;
ImageBrush bgbrush = new ImageBrush();
public Picture()
{
InitializeComponent();
// Define steps
Global.CreateSteps(Global.SelectedMenuOrder, this, ((MasterPage)System.Windows.Application.Current.MainWindow).StepsWindow);
// Create TempLocation
Directory.CreateDirectory(Settings.TempLocation);
// Handle the Canon EOS camera
Global.CameraHandler.ImageSaveDirectory = Settings.TempLocation;
SetImageAction = (BitmapImage img) => { bgbrush.ImageSource = img; };
// Configure the camera timer
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += DispatcherTimer_Tick;
dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 800);
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Subscribe to camera events
if (Global.CameraHandler != null)
{
Global.CameraHandler.LiveViewUpdated += CameraHandler_LiveViewUpdated;
Global.CameraHandler.ImageSaved += CameraHandler_ImageSaved;
Global.CameraHandler.CameraSDKError += CameraHandler_CameraSDKError;
}
// Start LiveView
try
{
Console.WriteLine(Global.CameraHandler.IsLiveViewOn);
if (!Global.CameraHandler.IsLiveViewOn)
{
CameraLiveView.Background = bgbrush;
Global.CameraHandler.StartLiveView();
}
}
catch (Exception)
{
// We cannot recover from that kind of errror. Reboot the application
CameraCrashHandler();
}
}
private void CameraTrigger_Click(object sender, RoutedEventArgs e)
{
// The user has clicked the trigger, change the layout
CameraTrigger.Visibility = System.Windows.Visibility.Collapsed;
CameraCountDown.Visibility = System.Windows.Visibility.Visible;
CameraTrigger.IsEnabled = false;
// Start the countdown
secondsToWait = 4;
dispatcherTimer.Start();
Global.WriteToLog("INFO", "Camera shutter pressed... waiting for camera to take picture!");
}
private void DispatcherTimer_Tick(object sender, EventArgs e)
{
// Handles the countdown
switch (secondsToWait)
{
case 4:
CameraTimer3.Foreground = new SolidColorBrush(Colors.White);
Global.PlaySound("pack://application:,,,/Resources/Audio/camera_beep.wav");
break;
case 3:
CameraTimer2.Foreground = new SolidColorBrush(Colors.White);
Global.PlaySound("pack://application:,,,/Resources/Audio/camera_beep.wav");
break;
case 2:
CameraTimer1.Foreground = new SolidColorBrush(Colors.White);
Global.PlaySound("pack://application:,,,/Resources/Audio/camera_beep.wav");
break;
case 1:
CameraTimer0.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/Images/icon_cameraWhite.png"));
Global.CameraFlashEffect(((MasterPage)System.Windows.Application.Current.MainWindow).CameraFlash);
Global.CameraHandler.TakePhoto();
break;
case 0:
CameraTimer0.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/Images/icon_cameraRed.png"));
CameraTimer1.Foreground = (SolidColorBrush)(new BrushConverter().ConvertFrom("#e8234a"));
CameraTimer2.Foreground = (SolidColorBrush)(new BrushConverter().ConvertFrom("#e8234a"));
CameraTimer3.Foreground = (SolidColorBrush)(new BrushConverter().ConvertFrom("#e8234a"));
dispatcherTimer.Stop();
break;
default:
break;
}
secondsToWait--;
}
private void Page_Unloaded(object sender, RoutedEventArgs e)
{
// Stop LiveView
if (Global.CameraHandler.IsLiveViewOn)
{
CameraLiveView.Background = null;
Global.CameraHandler.StopLiveView();
}
// Unsubscribe from events
Global.CameraHandler.LiveViewUpdated -= CameraHandler_LiveViewUpdated;
Global.CameraHandler.ImageSaved -= CameraHandler_ImageSaved;
Global.CameraHandler.CameraSDKError -= CameraHandler_CameraSDKError;
}
#region CameraHandler
void CameraHandler_CameraSDKError(string error)
{
// Handle known errors
Global.WriteToLog("ERROR", "CameraSDKError: " + error);
switch (error)
{
case "0x00008D01":
// Reset cameraTrigger for taking another photo
this.Dispatcher.Invoke((Action)(() =>
{
CameraTrigger.Visibility = System.Windows.Visibility.Visible;
CameraCountDown.Visibility = System.Windows.Visibility.Collapsed;
CameraTrigger.IsEnabled = true;
}));
break;
default:
CameraCrashHandler();
break;
}
}
void CameraHandler_ImageSaved(string img)
{
// Assign image to user
Global.PersonObject.Image = img;
// We have a image, let's navigate to the next page
this.Dispatcher.Invoke((Action)(() =>
{
NavigationService.Navigate(Global.FindPageByString(Global.NavigateManager(this, Functions.Enums.Navigation.Forward)));
}));
}
void CameraHandler_LiveViewUpdated(Stream img)
{
try
{
if (Global.CameraHandler.IsLiveViewOn)
{
using (WrappingStream s = new WrappingStream(img))
{
img.Position = 0;
BitmapImage EvfImage = new BitmapImage();
EvfImage.BeginInit();
EvfImage.StreamSource = s;
EvfImage.CacheOption = BitmapCacheOption.OnLoad;
EvfImage.EndInit();
EvfImage.Freeze();
Application.Current.Dispatcher.Invoke(SetImageAction, EvfImage);
}
}
}
catch (Exception ex)
{
Global.WriteToLog("ERROR", "LiveViewUpdated failed: " + ex.Message);
}
}
static void CameraCrashHandler()
{
// Camera cannot start
Global.WriteToLog("ERROR", "Unkown CameraSDKError. Automatic reboot needed");
MessageWindow mw = new MessageWindow("CameraErrorTitle", "CameraErrorMessage");
mw.ShowDialog();
// We cannot recover from that kind of errror. Reboot the application
System.Windows.Forms.Application.Restart();
System.Windows.Application.Current.Shutdown();
}
#endregion
}
}
第三页:
using PhotoBooth.Functions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace PhotoBooth.Pages
{
/// <summary>
/// Interaction logic for PreviewID.xaml
/// </summary>
public partial class PreviewID : Page
{
public PreviewID()
{
InitializeComponent();
// Define steps
Global.CreateSteps(Global.SelectedMenuOrder, this, ((MasterPage)System.Windows.Application.Current.MainWindow).StepsWindow);
// Load image and data
PreviewIDText = GetIDText(Global.PersonObject, PreviewIDText);
PreviewIDCard.Source = GetIDPhoto(Global.PersonObject);
PreviewIDPhoto.Background = new ImageBrush(new BitmapImage(new Uri(Global.PersonObject.Image)));
}
private void PreviewIDRetry_Click(object sender, RoutedEventArgs e)
{
Global.WriteToLog("INFO", "User did not approve the image, retry!");
NavigationService.Navigate(Global.FindPageByString(Global.NavigateManager(this, Functions.Enums.Navigation.Backward)));
}
private void PreviewIDAccept_Click(object sender, RoutedEventArgs e)
{
Global.WriteToLog("INFO", "User approved the image");
NavigationService.Navigate(Global.FindPageByString(Global.NavigateManager(this, Functions.Enums.Navigation.Forward)));
}
public TextBlock GetIDText(Functions.Enums.Person p, TextBlock tb)
{
tb.Text = "";
tb.FontSize = 24;
if (p.Affiliation == Functions.Enums.Affiliation.Employee)
{
// Ansatt
tb.Inlines.Add(new Run(p.FirstName + " " + p.LastName + Environment.NewLine) { FontWeight = FontWeights.Bold, FontSize = 30 });
tb.Inlines.Add(p.Title + Environment.NewLine);
tb.Inlines.Add(p.Department + Environment.NewLine);
tb.Inlines.Add("Ansatt nr: " + p.EmployeeNumber + Environment.NewLine);
}
else
{
// Student
tb.Inlines.Add("Last name: ");
tb.Inlines.Add(new Run(p.LastName + Environment.NewLine) { FontWeight = FontWeights.Bold });
tb.Inlines.Add("First name: ");
tb.Inlines.Add(new Run(p.FirstName + Environment.NewLine) { FontWeight = FontWeights.Bold });
tb.Inlines.Add("Date of birth: ");
tb.Inlines.Add(new Run("dd.mm.yyyy" + Environment.NewLine) { FontWeight = FontWeights.Bold });
tb.Inlines.Add("Studentnr: ");
tb.Inlines.Add(new Run("xxxxxx" + Environment.NewLine) { FontWeight = FontWeights.Bold });
}
return tb;
}
public BitmapImage GetIDPhoto(Functions.Enums.Person p)
{
BitmapImage result;
switch (p.Affiliation)
{
case PhotoBooth.Functions.Enums.Affiliation.Student:
result = new BitmapImage(new Uri("pack://application:,,,/Resources/Images/idcard_student.png"));
break;
case PhotoBooth.Functions.Enums.Affiliation.Employee:
result = new BitmapImage(new Uri("pack://application:,,,/Resources/Images/idcard_employee.png"));
break;
default:
result = new BitmapImage(new Uri("pack://application:,,,/Resources/Images/idcard_student.png"));
break;
}
return result;
}
private void Page_Unloaded(object sender, RoutedEventArgs e)
{
PreviewIDPhoto.Background = null;
}
}
}
虽然大多数全局函数在所有其他页面上都使用,但据我所知,正是这个页面给我带来了大部分麻烦。下面是我的内存性能测试截图。
- 用户从第1页导航到第2页(或其他页面),据我所见没有问题。内存使用量似乎很稳定。
- 用户触发带有LiveView的佳能相机(第2页)。内存消耗会上下波动,但保持稳定。
- 用户拍摄并获得预览图像(第3页),重试(返回第2页),再次拍摄,重试,以此类推......
每次加载后台代码: