Process[] processes = Process.GetProcesses()
- замануха, это только кажется, что все проще простого. А как только начнете разбираться а как же отобразить память, а как же отобразить % занятого процессора, сразу возникнет куча неразрешимых вопросов. Поэтому скажу сразу: единственное решение получения данных для вашей задачи - PerformanceCounter
.
Далее, обновляя таблицу руками вы подвешиваете UI, делаете это неоптимально. Все контролы в WPF заточены на работу с привязками данных, давайте использовать привязки данных.
Таймеры. При наличии такой великолепной штуки как асинхронное программирование с помощью async/await
- использовать таймеры сложно, поэтому я пойду так как проще.
Ну и плюс ко всему что выше, вы полностью очищаете таблицу в то время как 99% процессов на каждое обновление остаются те же самые. То есть гораздо эффективнее будет обновлять те что уже там есть, добавлять новые и удалять те что были закрыты.
Диспетчер задач "на минималках"
Для реализации примера использую WPF приложение на базе .NET 6
Вот такой XAML получился
<Window x:Class="WpfAppProcessView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppProcessView"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" d:DataContext="{d:DesignInstance local:MainWindow,IsDesignTimeCreatable=False}" Loaded="Window_Loaded" Closing="Window_Closing">
<Grid>
<DataGrid ItemsSource="{Binding Items}"
IsReadOnly="True"
AutoGenerateColumns="False"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
MinColumnWidth="80"
GridLinesVisibility="None"
AlternatingRowBackground="{StaticResource {x:Static SystemColors.ControlBrushKey}}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Header="Id"/>
<DataGridTextColumn Binding="{Binding DisplayName}" Header="Name" Width="*"/>
<DataGridTextColumn Binding="{Binding Processor, StringFormat={}{0:F1}%}" Header="CPU"/>
<DataGridTextColumn Binding="{Binding DisplayMemory}" Header="Memory" SortMemberPath="Memory"/>
</DataGrid.Columns>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Margin" Value="5,2"/>
</Style>
</DataGrid.CellStyle>
</DataGrid>
</Grid>
</Window>
Чтобы UI мог получать через привязки данных значения в режиме реального времени, нужно реализовать интерфейс INotifyPropertyChanged
.
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
И унаследоваться от этой реализации в модели данных, кстати, вот она.
public sealed class ProcessInfo : NotifyPropertyChanged
{
private long memory;
private CounterSample processorTime;
private double processor;
public ProcessInfo(long id, string name)
{
Id = id;
Name = name;
}
public long Id { get; }
public string Name { get; }
public string DisplayMemory
{
get
{
SizeUnit sizeUnit = SizeUnit.GetBestSizeUnit(Memory);
return $"{SizeUnit.Convert(Memory, SizeUnit.B, sizeUnit):G4} {sizeUnit.Name}";
}
}
public string DisplayName
{
get
{
int sharpIndex = Name.IndexOf('#');
return sharpIndex > 0 ? Name.Remove(sharpIndex) : Name;
}
}
public long Memory
{
get => memory;
set
{
if (memory == value)
return;
memory = value;
OnPropertyChanged();
OnPropertyChanged(nameof(DisplayMemory));
}
}
public double Processor
{
get => processor;
private set
{
if (processor == value)
return;
processor = value;
OnPropertyChanged();
}
}
public CounterSample ProcessorTime
{
get => processorTime;
set
{
long cpu = value.RawValue - processorTime.RawValue;
long time = value.TimeStamp - processorTime.TimeStamp;
Processor = cpu / (double)time * 100;
processorTime = value;
OnPropertyChanged();
}
}
}
Для удобной конвертации объема занятой оперативной памяти я использовал это решение.
public class SizeUnit
{
public string Name { get; }
public long ByteAmount { get; }
public SizeUnit(string name, long byteAmount)
{
Name = name;
ByteAmount = byteAmount;
}
private const long BytesInKiloByte = 1024L;
public static readonly SizeUnit B = new("B", 1L);
public static readonly SizeUnit KB = new("KB", BytesInKiloByte);
public static readonly SizeUnit MB = new("MB", BytesInKiloByte * BytesInKiloByte);
public static readonly SizeUnit GB = new("GB", BytesInKiloByte * BytesInKiloByte * BytesInKiloByte);
public static readonly SizeUnit TB = new("TB", BytesInKiloByte * BytesInKiloByte * BytesInKiloByte * BytesInKiloByte);
public static readonly SizeUnit[] All = { B, KB, MB, GB, TB };
public static SizeUnit GetBestSizeUnit(long value)
{
foreach (var sizeUnit in All)
{
if (value < sizeUnit.ByteAmount * BytesInKiloByte)
return sizeUnit;
}
return All[^1];
}
public static double Convert(long value, SizeUnit from, SizeUnit to)
=> value * (double)from.ByteAmount / to.ByteAmount;
}
В итоге, код окна получился такой
public partial class MainWindow : Window
{
private readonly ICollectionView _itemsView;
private readonly PerformanceCounterCategory _pcc;
private CancellationTokenSource _cts;
public ObservableCollection<ProcessInfo> Items { get; }
public MainWindow()
{
InitializeComponent();
DataContext = this;
Items = new();
_itemsView = CollectionViewSource.GetDefaultView(Items);
_pcc = new("Process");
}
private async void StartLoop()
{
if (_cts != null)
return;
try
{
using (_cts = new CancellationTokenSource())
{
await RunLoopAsync(_cts.Token);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, ex.GetType().Name);
}
_cts = null;
}
private void StopLoop()
{
_cts?.Cancel();
}
private async Task RunLoopAsync(CancellationToken token)
{
try
{
while (true)
{
await UpdateProcesses();
await Task.Delay(1000, token);
}
}
catch (OperationCanceledException)
{ }
}
private async Task UpdateProcesses()
{
InstanceDataCollectionCollection counters = await Task.Run(_pcc.ReadCategory);
InstanceDataCollection ids = counters["id process"];
InstanceDataCollection mem = counters["working set - private"];
InstanceDataCollection cpu = counters["% processor time"];
foreach (InstanceData data in ids.Values)
{
if (data.InstanceName == "_Total" || data.InstanceName == "Idle")
continue;
long id = data.RawValue;
ProcessInfo info = Items.FirstOrDefault(x => x.Id == id);
if (info is null)
{
info = new ProcessInfo(id, data.InstanceName);
Items.Add(info);
}
info.Memory = mem[data.InstanceName].RawValue;
info.ProcessorTime = cpu[data.InstanceName].Sample;
}
foreach (ProcessInfo info in Items.Where(item => ids[item.Name] is null).ToArray())
{
Items.Remove(info);
}
_itemsView.Refresh();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
StartLoop();
}
private void Window_Closing(object sender, CancelEventArgs e)
{
StopLoop();
}
}
Для реализации асинхронного бесконечного цикла, срабатывающего раз в секунду, я использовал это решение.
Запускаю, и получаю вот такой вывод
