【发布时间】:2018-05-03 08:45:35
【问题描述】:
我是响应式扩展世界的新手,我仍在努力学习。
我正在开发一个带有显示某些正在运行的 Windows 进程及其内存使用情况的数据网格的应用程序。每个进程的内存使用情况应该经常更新,即每 200 毫秒。
要求
- 选中复选框时
- 数据网格应该被进程填充,并且内存使用情况会使用间隔为 200 毫秒的计时器进行更新。
- 监视器(所有都应该在后台线程上完成)
--如果一个进程已经退出,它应该从源中移除。
--如果一个进程启动,它应该被添加到源中
-- 用于更改的文件
- 当复选框未选中时
- 应停止所有监视器活动
- 数据网格被清除
任何帮助将不胜感激! 备注:
- In the past 我尝试了几种方法,例如使用 ObservableConcurrentDictionary 作为资源和定期更新资源的计时器,但是我遇到了麻烦(并发、锁定等),所以我想有一个基于 Rx/ReactiveUI 的解决方案
- 受技术限制,我只能使用 .NET Framework 4.0,Reactive-core.Net40
更新
视图模型
private ReactiveList<IProcessModel> _processes = new ReactiveList<IProcessModel>() { ChangeTrackingEnabled = true };
public ReactiveList<IProcessModel> Processes { get { return _processes; } }
public MainViewModel(IMonitorService monitorService)
{
this.WhenAnyValue(vm => vm.ShowProcessesIsChecked).Subscribe((b) => DoShowProcesses(b));
}
private void DoShowProcesses(bool checkboxChecked)
{
IDisposable timer;
Processes.Clear();
if (checkboxChecked)
{
//checkbox checked
lock (Processes)
Processes.AddRange(_monitorService.GetProcesses());
timer = Observable.Timer(TimeSpan.FromMilliseconds(200.0))
.Select(x =>
{
lock (Processes)
{
foreach (var process in Processes) //throws the 'Collection was modified; enumeration operation may not execute.'
process.UpdateMemory();
return Processes.Where(p => p.ProcessObject.HasExited).ToList();
}
}).
ObserveOnDispatcher()
.Subscribe(processesExited =>
{
if (processesExited.Count() > 0)
{
lock (Processes)
Processes.RemoveAll(processesExited); //remove all processes that have exited
}
});
}
else
{
if (timer != null)
timer.Dispose();
}
}
我开了一个new thread
原创
视图模型
public class MainViewModel : ReactiveObject
{
public ReactiveList<IProcessModel> Processes { get; private set; }
IMonitorService _monitorService;
public MainViewModel(IMonitorService monitorService)
{
_monitorService = monitorService;
Processes = new ReactiveList<IProcessModel>() { ChangeTrackingEnabled = true };
this.WhenAnyValue(vm => vm.ShowProcessesIsChecked)
.Where(value => value == true) //checkbox checked
.ObserveOn(Scheduler.Default) //raise notifications on thread-pool thread to keep UI responsive
.Select((isChecked) =>
{
return monitorService.GetProcesses();
})
.ObserveOn(SynchronizationContext.Current)
.Subscribe(processes => {
Processes.AddRange(processes); }
);
//start the MonitorService with MonitorService.Start(Processes)
//start a timer with an interval of 200ms --> at interval
//- do UpdateMemory() foreach IProcessModel in Processes
//- if ProcessObject.HasExited --> remove it from the collection source
;
this.WhenAnyValue(vm => vm.ShowProcessesIsChecked)
.Where(value => value == false) //checkbox unchecked
.Subscribe((isChecked) =>
{
monitorService.Stop(); //this stops monitoring for starting processes and clears the Processes
});
}
private bool _showProcessesIsChecked;
public bool ShowProcessesIsChecked
{
get { return _showProcessesIsChecked; }
set { this.RaiseAndSetIfChanged(ref _showProcessesIsChecked, value); }
}
}
型号
public class ProcessModel : ProcessModelBase, IProcessModel
{
public ProcessModel(Process process)
{
ProcessObject = process;
}
public void UpdateMemory()
{
try
{
if (!ProcessObject.HasExited)
{
long mem = ProcessObject.PagedMemorySize64;
ProcessObject.Refresh();
if (mem != ProcessObject.PagedMemorySize64)
OnPropertyChanged(nameof(ProcessObject));
}
}
catch (Exception)
{
//log it
}
}
}
服务
public class MonitorService : IMonitorService
{
ManagementEventWatcher managementEventWatcher;
ReactiveList<IProcessModel> _processes;
public List<IProcessModel> GetProcesses()
{
List<IProcessModel> processes = new List<IProcessModel>();
foreach (var process in Process.GetProcesses().Where(p => p.ProcessName.Contains("chrome")))
processes.Add(new ProcessModel(process));
return processes;
}
/// <summary>
/// Starts the manager. Monitor a starting process and changes in log file
/// </summary>
/// <param name="processes"></param>
public void Start(ReactiveList<IProcessModel> processes)
{
_processes = processes;
var qStart = "SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName like 'chrome'";
managementEventWatcher = new ManagementEventWatcher(new WqlEventQuery(qStart));
managementEventWatcher.EventArrived += new EventArrivedEventHandler(OnProcessStarted);
try
{
managementEventWatcher.Start();
}
catch (Exception)
{
//log it
}
Task.Factory.StartNew(() => MonitorLogFile());
}
public void Stop()
{
if (managementEventWatcher != null)
managementEventWatcher.Stop();
if (_processes != null)
_processes.Clear();
}
private void MonitorLogFile()
{
//this code monitors a log file for changes. It is possible that the IsChecked property of a ProcessModel object is set in the Processes collection
}
private void OnProcessStarted(object sender, EventArrivedEventArgs e)
{
try
{
Process process = Process.GetProcessById(Convert.ToInt32(e.NewEvent.Properties["ProcessID"].Value));
_processes.Add(new ProcessModel(process));
}
catch (ArgumentException)
{
//log it
}
catch (InvalidOperationException)
{
//log it
}
}
}
XAML
<CheckBox Content='Show Processes' IsChecked='{Binding ShowProcessesIsChecked}' />
<DataGrid ItemsSource="{Binding Processes}">
<DataGrid.Resources>
<DataGridTemplateColumn Header='Process'
x:Key='dgProcessName'
IsReadOnly='True'
x:Shared='False'>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation='Horizontal' VerticalAlignment='Center'>
<CheckBox IsChecked='{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}' HorizontalAlignment='Stretch' VerticalAlignment='Stretch'> </CheckBox>
<TextBlock Text='{Binding ProcessObject.ProcessName}' />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="PID"
Binding="{Binding ProcessObject.Id}"
IsReadOnly='True'
x:Key='dgPID'
x:Shared='False' />
<DataGridTextColumn Header="Commit Size"
Binding='{Binding ProcessObject.PagedMemorySize64}'
IsReadOnly='True'
x:Key='dgCommitSize'
x:Shared='False' />
</DataGrid.Resources>
</DataGrid>
【问题讨论】:
-
你真的不应该锁定一个公共变量。这通常被认为是不好的。
-
另外,你为什么要锁定?使用 Rx 通常可以消除锁定,但如果你必须这样做,那么有一些很棒的集合可以为你做锁定。
标签: c# wpf mvvm system.reactive reactiveui