0

Я тут писал некое подобие процесс менеджера, и написал такой код для обновления процессов внутри DataGrid:

public MainWindow()
{
    InitializeComponent();
DispatcherTimer? timer;
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += new EventHandler(ticker);
timer.Start();

}

private void ticker(object? sender, EventArgs e) { Thread t = new Thread(new ThreadStart(timer_Tick)); t.Start(); }

private void timer_Tick() { proccessesgrid.Items.Clear(); Process[] processes = Process.GetProcesses(); proccessesgrid.Dispatcher.Invoke(() => { foreach (Process process in processes) { proccessesgrid.Items.Add(new appendprocess { process_icon = "/test/", process_pid = process.Id.ToString(), process_name = process.ProcessName, process_cpu = "test%", process_ram = "testMB", process_internet = "test%" }); } }); }

до того как я начал создавать новый поток для timer_tick все работало, однако раз в секунду приложение подвисало, однако после того как я добавил многопоточность, код вообще перестал работать, я попробовал добавить proccessesgrid.Dispatcher.Invoke однако это не помогло, при запуске приложения в DataGrid процессы попросту не добавляються, что делать?

  • 1
    proccessesgrid.Items.Clear() надо тоже в основном потоке делать. И не перед получением нового списка процессов, а после. – Pavel Mayorov Apr 26 '23 at 13:20
  • И не создавайте по новому потоку каждую секунду, лучше используйте System.Threading.Timer (это таймер, уже являющийся потоком). – Pavel Mayorov Apr 26 '23 at 13:21
  • Я перенес proccessesgrid.Items.Clear() в proccessesgrid.Dispatcher.Invoke, однако ничего не изменилось –  Apr 26 '23 at 13:58
  • Слона то я и не приметил. Используете DispatcherTimer, который специально заточен для работы в UI потоке, чтобы потом запустить обработчик принудительно в потоке. Если уж на то пошлом, можно было System.Threading.Timer использовать. Завтра посмотрю еще раз, что здесь можно сделать, и как оно подвисает. Думаю надо кеширование добавлять и привязки данных использовать. В режиме низкоуровневого доступа к таблице, ничего "в лоб" не получится. Кстати, покажите XAML разметку DataGrid, а то непонятно, какие там настройки и колонки. – aepot Apr 26 '23 at 21:31
  • Там много кода, так что наверное проще будет скинуть сразу весь проект: https://drive.google.com/file/d/1fe_iY4o-urnwC1jN_pLw4I46bpQ_GjYv/view?usp=sharing –  Apr 27 '23 at 10:18

1 Answers1

0

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)
    =&gt; 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 $&quot;{SizeUnit.Convert(Memory, SizeUnit.B, sizeUnit):G4} {sizeUnit.Name}&quot;;
    }
}

public string DisplayName
{
    get
    {
        int sharpIndex = Name.IndexOf('#');
        return sharpIndex &gt; 0 ? Name.Remove(sharpIndex) : Name;
    }
}

public long Memory
{
    get =&gt; memory;
    set
    {
        if (memory == value)
            return;
        memory = value;
        OnPropertyChanged();
        OnPropertyChanged(nameof(DisplayMemory));
    }
}

public double Processor
{
    get =&gt; processor;
    private set
    {
        if (processor == value)
            return;
        processor = value;
        OnPropertyChanged();
    }
}

public CounterSample ProcessorTime
{
    get =&gt; 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(&quot;B&quot;, 1L);
public static readonly SizeUnit KB = new(&quot;KB&quot;, BytesInKiloByte);
public static readonly SizeUnit MB = new(&quot;MB&quot;, BytesInKiloByte * BytesInKiloByte);
public static readonly SizeUnit GB = new(&quot;GB&quot;, BytesInKiloByte * BytesInKiloByte * BytesInKiloByte);
public static readonly SizeUnit TB = new(&quot;TB&quot;, 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 &lt; sizeUnit.ByteAmount * BytesInKiloByte)
            return sizeUnit;
    }
    return All[^1];
}

public static double Convert(long value, SizeUnit from, SizeUnit to)
    =&gt; value * (double)from.ByteAmount / to.ByteAmount;

}

В итоге, код окна получился такой

public partial class MainWindow : Window
{
    private readonly ICollectionView _itemsView;
    private readonly PerformanceCounterCategory _pcc;
    private CancellationTokenSource _cts;
public ObservableCollection&lt;ProcessInfo&gt; Items { get; }

public MainWindow()
{
    InitializeComponent();
    DataContext = this;
    Items = new();
    _itemsView = CollectionViewSource.GetDefaultView(Items);
    _pcc = new(&quot;Process&quot;);
}

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[&quot;id process&quot;];
    InstanceDataCollection mem = counters[&quot;working set - private&quot;];
    InstanceDataCollection cpu = counters[&quot;% processor time&quot;];

    foreach (InstanceData data in ids.Values)
    {
        if (data.InstanceName == &quot;_Total&quot; || data.InstanceName == &quot;Idle&quot;)
            continue;
        long id = data.RawValue;
        ProcessInfo info = Items.FirstOrDefault(x =&gt; 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 =&gt; 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();
}

}

Для реализации асинхронного бесконечного цикла, срабатывающего раз в секунду, я использовал это решение.

Запускаю, и получаю вот такой вывод

введите сюда описание изображения

aepot
  • 49,560