Я занимаюсь разработкой приложения с сеткой данных, в которой отображаются определенные запущенные процессы Windows (в моем примере процессы Chrome).Сетка данных загружается с процессами, когда установлен флажок.
Требования:
- Отображение «живой» информации для имени, использования памяти (частный рабочий набор) каждого процесса, как и в диспетчере задач Windows - вкладка «Процессы».
- Отслеживание процессов, которые завершаются, и удаление их из таблицы данных.
- Отслеживание определенных процессов, которые запускаются.
Используемые приемы:
Проблема:
- Когда процессы загружаются, загрузка ЦП становится очень высокой, а пользовательский интерфейс почти зависает.
- Загрузка ЦП остается высокой даже при вызове
ManagerService.Stop()
. - Иногда
System.InvalidOperationException - Cannot change ObservableCollection during a CollectionChanged event
исключение выдается при удалении процесса из коллекцииn.
Как я могу решить эту проблему?Кроме того, мой подход - это «хорошая практика»?
Любая помощь будет принята с благодарностью!Я уже потратил много времени на эту проблему.
Обновление 1
Не помогло, удаление OnRendering()
и внедрениеINotifyPropertyChanged
public class CustomProcess : INotifyPropertyChanged
{
private double _memory;
public double Memory
{
get { return _memory; }
set
{
if (_memory != value)
{
_memory = value;
OnPropertyChanged(nameof(Memory));
}
}
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked != value)
{
_isChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
Обновление 2
Следуя советам Evk, я обновился
- Используется регулярноObservableCollection
- переместил таймер в viewmodel
Использование ЦП теперь намного ниже.Однако иногда я получаю исключение Process with an ID of ... is not running
в OnProcessStarted()
Viewmodel
public class MainViewModel
{
System.Threading.Timer timer;
private ObservableCollection<CustomProcess> _processes;
public ObservableCollection<CustomProcess> Processes
{
get
{
if (_processes == null)
_processes = new ObservableCollection<CustomProcess>();
return _processes;
}
}
private void OnBooleanChanged(PropertyChangedMessage<bool> propChangedMessage)
{
if (propChangedMessage.NewValue == true)
{
_managerService.Start(_processes);
timer = new System.Threading.Timer(OnTimerTick, null, 0, 200); //every 200ms
ProcessesIsVisible = true;
}
else
{
timer.Dispose();
_managerService.Stop();
ProcessesIsVisible = false;
}
}
private void OnTimerTick(object state)
{
try
{
for (int i = 0; i < Processes.Count; i++)
Processes[i].UpdateMemory();
}
catch (Exception)
{
}
}
Model
public class CustomProcess : INotifyPropertyChanged
{
public void UpdateMemory()
{
if (!ProcessObject.HasExited)
Memory = Process.GetProcessById(ProcessObject.Id).PagedMemorySize64;
}
private double _memory;
public double Memory
{
get { return _memory; }
set
{
if (_memory != value)
{
_memory = value;
OnPropertyChanged(nameof(Memory));
}
}
}
Сервис
private void OnProcessNotification(NotificationMessage<Process> notMessage)
{
if (notMessage.Notification == "exited")
{
_processes.Remove(p => p.ProcessObject.Id == notMessage.Content.Id, DispatcherHelper.UIDispatcher);
}
}
Оригинальный код
XAML
<DataGrid ItemsSource="{Binding Processes}">
<DataGridTextColumn Header="Process name"
Binding="{Binding ProcessObject.ProcessName}"
IsReadOnly='True'
Width='Auto' />
<DataGridTextColumn Header="PID"
Binding="{Binding ProcessObject.Id}"
IsReadOnly='True'
Width='Auto' />
<DataGridTextColumn Header="Memory"
Binding='{Binding Memory}'
IsReadOnly='True'
Width='Auto' />
</DataGrid>
XAML Код позади
public MainWindow()
{
InitializeComponent();
DataContext = SimpleIoc.Default.GetInstance<MainViewModel>();
CompositionTarget.Rendering += OnRendering;
}
private void OnRendering(object sender, EventArgs e)
{
if (DataContext is IRefresh)
((IRefresh)DataContext).Refresh();
}
}
ViewModel
public class MainViewModel : Shared.ViewModelBase, IRefresh
{
private AsyncObservableCollection<CustomProcess> _processes;
public AsyncObservableCollection<CustomProcess> Processes
{
get
{
if (_processes == null)
_processes = new AsyncObservableCollection<CustomProcess>();
return _processes;
}
}
private readonly IManagerService _managerService;
public MainViewModel(IManagerService managerService)
{
_managerService = managerService;
Messenger.Default.Register<PropertyChangedMessage<bool>>(this, OnBooleanChanged);
}
#region PropertyChangedMessage
private void OnBooleanChanged(PropertyChangedMessage<bool> propChangedMessage)
{
if (propChangedMessage.NewValue == true)
{
_managerService.Start(_processes);
}
else
{
_managerService.Stop();
}
}
public void Refresh()
{
foreach (var process in Processes)
RaisePropertyChanged(nameof(process.Memory)); //notify UI that the property has changed
}
Сервис
public class ManagerService : IManagerService
{
AsyncObservableCollection<CustomProcess> _processes;
ManagementEventWatcher managementEventWatcher;
public ManagerService()
{
Messenger.Default.Register<NotificationMessage<Process>>(this, OnProcessNotification);
}
private void OnProcessNotification(NotificationMessage<Process> notMessage)
{
if (notMessage.Notification == "exited")
{
//a process has exited. Remove it from the collection
_processes.Remove(p => p.ProcessObject.Id == notMessage.Content.Id);
}
}
/// <summary>
/// Starts the manager. Add processes and monitor for starting processes
/// </summary>
/// <param name="processes"></param>
public void Start(AsyncObservableCollection<CustomProcess> processes)
{
_processes = processes;
_processes.CollectionChanged += OnCollectionChanged;
foreach (var process in Process.GetProcesses().Where(p => p.ProcessName.Contains("chrome")))
_processes.Add(new CustomProcess(process));
MonitorStartedProcess();
Task.Factory.StartNew(() => MonitorLogFile());
}
/// <summary>
/// Stops the manager.
/// </summary>
public void Stop()
{
_processes.CollectionChanged -= OnCollectionChanged;
managementEventWatcher = null;
_processes = null;
}
private void MonitorLogFile()
{
//this code monitors a log file for changes. It is possible that the IsChecked property of a CustomProcess object is set in the Processes collection
}
/// <summary>
/// Monitor for started Chrome
/// </summary>
private void MonitorStartedProcess()
{
var qStart = "SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName like '%chrome%'";
ManagementEventWatcher managementEventWatcher = new ManagementEventWatcher(new WqlEventQuery(qStart));
managementEventWatcher.EventArrived += new EventArrivedEventHandler(OnProcessStarted);
try
{
managementEventWatcher.Start();
}
catch (Exception)
{
}
}
private void OnProcessStarted(object sender, EventArrivedEventArgs e)
{
try
{
int pid = Convert.ToInt32(e.NewEvent.Properties["ProcessID"].Value);
_processes.Add(new CustomProcess(Process.GetProcessById(pid))); //add to collection
}
catch (Exception)
{
}
}
Модель
public class CustomProcess
{
public Process ProcessObject { get; }
public CustomProcess(Process process)
{
ProcessObject = process;
try
{
ProcessObject.EnableRaisingEvents = true;
ProcessObject.Exited += ProcessObject_Exited;
Task.Factory.StartNew(() => UpdateMemory());
}
catch (Exception)
{
}
}
private void ProcessObject_Exited(object sender, EventArgs e)
{
Process process = sender as Process;
NotificationMessage<Process> notMessage = new NotificationMessage<Process>(process, "exited");
Messenger.Default.Send(notMessage); //send a notification that the process has exited
}
private void UpdateMemory()
{
while (!ProcessObject.HasExited)
{
try
{
Memory = Process.GetProcessById(ProcessObject.Id).PagedMemorySize64;
}
catch (Exception)
{
}
}
}
private double _memory;
public double Memory
{
get { return _memory; }
set
{
if (_memory != value)
{
_memory = value;
}
}
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked != value)
{
_isChecked = value;
}
}
}