【发布时间】:2018-04-26 01:25:02
【问题描述】:
我正在开发一个应用程序,其数据网格显示某些正在运行的 Windows 进程(在我的示例 Chrome 进程中)。 选中复选框时,数据网格会加载进程。
要求:
- 显示每个进程的名称、内存使用情况(私有工作集)的“实时”信息,就像在 Windows 任务管理器 - 进程选项卡中一样。
- 监控退出并从数据网格中删除它们的进程。
- 监控某些启动的进程。
使用过的技术:
- MVVM
- MVVM 灯
- Benoît Blanchon approach 用于快速变化的属性
- Thomas Levesque AsyncObservableCollection 从另一个线程修改 ObservableCollection
问题:
- 加载进程时,CPU 使用率变得非常高,用户界面几乎死机。
- 即使调用了
ManagerService.Stop(),CPU 使用率仍然很高。 - 从集合中删除进程时,有时会引发
System.InvalidOperationException - Cannot change ObservableCollection during a CollectionChanged event异常。
我该如何解决这个问题?我的方法也是一种“良好做法”吗?
任何帮助将不胜感激!我已经在这个问题上花费了很多时间。
更新 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
- 将计时器移至视图模型
CPU 使用率现在要低得多。
但是我有时会在OnProcessStarted() 中遇到Process with an ID of ... is not running 异常
视图模型
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)
{
}
}
型号
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();
}
}
视图模型
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;
}
}
}
【问题讨论】:
-
我相信你不需要
OnRendering在代码后面的方法和Refresh在 ViewModel 中。您应该使用 WPF 通知系统 --- 要实现这一点,您应该在CustomProcess类中实现INotifyProerptyChanged。因此,如果源值发生变化,WPF 引擎本身会刷新绑定的列值。 -
没有更改通知的好方法。没有它,就无法正确实现 MVVM。
-
@user1672994 谢谢,我已经尝试过了(实现了 INotifyPropertyChanged,删除了 OnRendering()),但效果相同,CPU 使用率高并且几乎冻结 UI。
-
这里有几个问题,但主要问题是:您真的需要经常更新流程统计信息吗?说每 200 毫秒还不够好吗?
-
@Evk 200 毫秒就足够了,确实
标签: c# .net wpf multithreading mvvm