首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 實作任務管理器:Process 類別枚舉進程、記憶體/CPU 監控、啟動路徑開啟

     編輯:關於C語言

前言

透過 System.Diagnostics.Process 可建立類似 Windows 工作管理員的進程監控工具。本文實作進程列表即時刷新、資源使用率顯示、右鍵選單操作 (結束進程、開啟檔案位置、屬性)。

核心資料模型


public class ProcessInfo
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string ProcessName { get; set; }
    public long WorkingSet64 { get; set; }      // 實體記憶體
    public long VirtualMemorySize64 { get; set; } // 虛擬記憶體
    public TimeSpan TotalProcessorTime { get; set; }
    public int BasePriority { get; set; }
    public bool Responding { get; set; }
    public string FilePath { get; set; }
    public string CompanyName { get; set; }
    public string Description { get; set; }
    public DateTime StartTime { get; set; }
    public ProcessThreadCollection Threads { get; set; }
    public ProcessModuleCollection Modules { get; set; }

    // 格式化顯示用
    public string WorkingSetDisplay => FormatBytes(WorkingSet64);
    public string VirtualMemoryDisplay => FormatBytes(VirtualMemorySize64);
    public string CpuTimeDisplay => TotalProcessorTime.ToString(@"hh\:mm\:ss");
    
    private static string FormatBytes(long bytes)
    {
        string[] units = { "B", "KB", "MB", "GB", "TB" };
        int i = 0;
        double size = bytes;
        while (size >= 1024 && i < units.Length - 1) { size /= 1024; i++; }
        return $"{size:0.##} {units[i]}";
    }
}

進程枚舉與資料擷取


using System.Diagnostics;
using System.Management; // 需參考 System.Management.dll

public static class ProcessMonitor
{
    private static readonly string[] _systemProcessNames = 
    { "System", "Idle", "Registry", "smss", "csrss", "wininit", "services", "lsass" };

    public static List GetAllProcesses()
    {
        var list = new List();
        Process[] processes = Process.GetProcesses();
        
        foreach (Process proc in processes)
        {
            try
            {
                // 系統核心進程無法存取詳細資訊,跳過或標記
                if (_systemProcessNames.Contains(proc.ProcessName))
                {
                    list.Add(new ProcessInfo 
                    { 
                        Id = proc.Id, 
                        ProcessName = proc.ProcessName, 
                        Name = "[System Process]" 
                    });
                    continue;
                }

                proc.Refresh(); // 更新快照
                
                var info = new ProcessInfo
                {
                    Id = proc.Id,
                    ProcessName = proc.ProcessName,
                    Name = GetProcessDisplayName(proc),
                    WorkingSet64 = proc.WorkingSet64,
                    VirtualMemorySize64 = proc.VirtualMemorySize64,
                    TotalProcessorTime = proc.TotalProcessorTime,
                    BasePriority = proc.BasePriority,
                    Responding = proc.Responding,
                    FilePath = GetProcessPath(proc),
                    StartTime = proc.StartTime,
                    Threads = proc.Threads,
                    Modules = proc.Modules
                };
                
                // 取得檔案版本資訊
                if (!string.IsNullOrEmpty(info.FilePath))
                {
                    try 
                    {
                        var fvi = FileVersionInfo.GetVersionInfo(info.FilePath);
                        info.CompanyName = fvi.CompanyName;
                        info.Description = fvi.FileDescription;
                    }
                    catch { }
                }
                
                list.Add(info);
            }
            catch (System.ComponentModel.Win32Exception)
            {
                // Access denied (權限不足)
                list.Add(new ProcessInfo 
                { 
                    Id = proc.Id, 
                    ProcessName = proc.ProcessName, 
                    Name = "[Access Denied]" 
                });
            }
            catch (Exception)
            {
                // 其他錯誤
            }
        }
        
        return list.OrderBy(p => p.ProcessName).ToList();
    }

    private static string GetProcessDisplayName(Process proc)
    {
        try 
        {
            string desc = FileVersionInfo.GetVersionInfo(proc.MainModule.FileName).FileDescription;
            return string.IsNullOrEmpty(desc) ? proc.ProcessName : desc;
        }
        catch { return proc.ProcessName; }
    }

    private static string GetProcessPath(Process proc)
    {
        try { return proc.MainModule.FileName; }
        catch { return string.Empty; }
    }
}

WinForms 即時刷新 ListView (解決閃爍與選取保持)


public partial class TaskManagerForm : Form
{
    private ListView _listView;
    private Timer _refreshTimer;
    private int _selectedPid = -1;
    private readonly object _updateLock = new();

    public TaskManagerForm()
    {
        InitializeListView();
        _refreshTimer = new Timer { Interval = 2000 }; // 2 秒刷新
        _refreshTimer.Tick += RefreshTimer_Tick;
        _refreshTimer.Start();
        LoadProcesses();
    }

    private void InitializeListView()
    {
        _listView = new ListView
        {
            Dock = DockStyle.Fill,
            View = View.Details,
            FullRowSelect = true,
            GridLines = true,
            HideSelection = false,
            DoubleBuffered = true // 關鍵:防止閃爍 (需反射設定或繼承)
        };
        
        _listView.Columns.Add("PID", 60);
        _listView.Columns.Add("名稱", 180);
        _listView.Columns.Add("進程名", 120);
        _listView.Columns.Add("記憶體", 100);
        _listView.Columns.Add("虛擬記憶體", 100);
        _listView.Columns.Add("CPU 時間", 80);
        _listView.Columns.Add("優先序", 60);
        _listView.Columns.Add("狀態", 80);
        _listView.Columns.Add("路徑", 300);
        
        _listView.MouseClick += ListView_MouseClick;
        Controls.Add(_listView);
    }

    private async void RefreshTimer_Tick(object sender, EventArgs e)
    {
        await LoadProcessesAsync();
    }

    private async Task LoadProcessesAsync()
    {
        // 背景執行緒取得資料
        var processes = await Task.Run(() => ProcessMonitor.GetAllProcesses());
        
        // UI 執行緒更新
        if (InvokeRequired)
        {
            Invoke(new Action(() => UpdateListView(processes)));
        }
        else
        {
            UpdateListView(processes);
        }
    }

    private void UpdateListView(List processes)
    {
        lock (_updateLock)
        {
            _listView.BeginUpdate();
            try
            {
                // 建立現有項目查找表 (Key: PID)
                var existingItems = _listView.Items.Cast()
                    .ToDictionary(item => (int)item.Tag, item => item);

                foreach (var proc in processes)
                {
                    if (existingItems.TryGetValue(proc.Id, out var item))
                    {
                        // 更新現有項目
                        UpdateListViewItem(item, proc);
                        existingItems.Remove(proc.Id);
                    }
                    else
                    {
                        // 新增項目
                        var newItem = CreateListViewItem(proc);
                        _listView.Items.Add(newItem);
                    }
                }
                
                // 移除已結束的進程
                foreach (var orphan in existingItems.Values)
                    orphan.Remove();
                
                // 恢復選取
                if (_selectedPid > 0)
                {
                    var selItem = _listView.Items.Cast()
                        .FirstOrDefault(item => (int)item.Tag == _selectedPid);
                    if (selItem != null)
                    {
                        selItem.Selected = true;
                        selItem.EnsureVisible();
                    }
                }
            }
            finally
            {
                _listView.EndUpdate();
            }
        }
    }

    private ListViewItem CreateListViewItem(ProcessInfo proc)
    {
        var item = new ListViewItem(proc.Id.ToString()) { Tag = proc.Id };
        item.SubItems.Add(proc.Name);
        item.SubItems.Add(proc.ProcessName);
        item.SubItems.Add(proc.WorkingSetDisplay);
        item.SubItems.Add(proc.VirtualMemoryDisplay);
        item.SubItems.Add(proc.CpuTimeDisplay);
        item.SubItems.Add(proc.BasePriority.ToString());
        item.SubItems.Add(proc.Responding ? "正在執行" : "未回應");
        item.SubItems.Add(proc.FilePath ?? "N/A");
        return item;
    }

    private void UpdateListViewItem(ListViewItem item, ProcessInfo proc)
    {
        item.SubItems[1].Text = proc.Name;
        item.SubItems[2].Text = proc.ProcessName;
        item.SubItems[3].Text = proc.WorkingSetDisplay;
        item.SubItems[4].Text = proc.VirtualMemoryDisplay;
        item.SubItems[5].Text = proc.CpuTimeDisplay;
        item.SubItems[6].Text = proc.BasePriority.ToString();
        item.SubItems[7].Text = proc.Responding ? "正在執行" : "未回應";
        item.SubItems[8].Text = proc.FilePath ?? "N/A";
    }

    private void ListView_MouseClick(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Right && _listView.SelectedItems.Count > 0)
        {
            _selectedPid = (int)_listView.SelectedItems[0].Tag;
            ShowContextMenu(_listView.SelectedItems[0]);
        }
    }

    private void ShowContextMenu(ListViewItem item)
    {
        var menu = new ContextMenuStrip();
        int pid = (int)item.Tag;
        
        menu.Items.Add("結束進程", null, (s, e) => KillProcess(pid));
        menu.Items.Add("開啟檔案位置", null, (s, e) => OpenFileLocation(item.SubItems[8].Text));
        menu.Items.Add("屬性", null, (s, e) => ShowProperties(item.SubItems[8].Text));
        menu.Items.Add("-");
        menu.Items.Add("複製 PID", null, (s, e) => Clipboard.SetText(pid.ToString()));
        
        menu.Show(_listView, _listView.PointToClient(Cursor.Position));
    }

    private void KillProcess(int pid)
    {
        try
        {
            var proc = Process.GetProcessById(pid);
            proc.Kill();
            proc.WaitForExit(2000);
        }
        catch (Exception ex)
        {
            MessageBox.Show($"無法結束進程: {ex.Message}");
        }
    }

    private void OpenFileLocation(string filePath)
    {
        if (string.IsNullOrEmpty(filePath) || filePath == "N/A") return;
        
        try
        {
            // 關鍵:使用 explorer /select 選中檔案
            Process.Start("explorer.exe", $"/select,\"{filePath}\"");
        }
        catch (Exception ex)
        {
            MessageBox.Show($"無法開啟位置: {ex.Message}");
        }
    }

    private void ShowProperties(string filePath)
    {
        if (string.IsNullOrEmpty(filePath) || filePath == "N/A") return;
        
        try
        {
            // 使用 ShellExecute 顯示檔案屬性對話框
            var psi = new ProcessStartInfo
            {
                FileName = filePath,
                Verb = "properties",
                UseShellExecute = true
            };
            Process.Start(psi);
        }
        catch { /* 屬性動詞不支援時靜默失敗 */ }
    }
}

關鍵技術點解析

問題解決方案
ListView 閃爍設定 DoubleBuffered = true (需反射或繼承)、BeginUpdate/EndUpdate 批次更新
刷新後選取遺失記錄 SelectedPid,更新後按 PID 恢復選取
系統進程 Access Deniedtry-catch 捕捉 Win32Exception,標記為系統進程
開啟檔案位置不選中檔案使用 explorer /select,"path" 而非單純開啟資料夾
高 CPU 佔用刷新間隔 2-5 秒、背景執行緒取資料、僅更新變更項目

進階功能:CPU 使用率計算


public class CpuTracker
{
    private readonly Dictionary _lastSample = new();

    public float GetCpuUsage(ProcessInfo proc)
    {
        var now = DateTime.UtcNow;
        var currentCpu = proc.TotalProcessorTime;
        
        if (_lastSample.TryGetValue(proc.Id, out var last))
        {
            double cpuMs = (currentCpu - last.CpuTime).TotalMilliseconds;
            double elapsedMs = (now - last.Timestamp).TotalMilliseconds;
            int coreCount = Environment.ProcessorCount;
            
            // 公式: (CPU時間差 / 經過時間) / 核心數 * 100%
            float usage = (float)((cpuMs / elapsedMs) / coreCount * 100);
            _lastSample[proc.Id] = (currentCpu, now);
            return Math.Clamp(usage, 0, 100 * coreCount);
        }
        
        _lastSample[proc.Id] = (currentCpu, now);
        return 0;
    }
}

總結

任務管理器核心在於:Process.GetProcesses() 枚舉、Process.Refresh() 更新快照、MainModule.FileName 取得路徑、explorer /select 開啟位置。UI 端關鍵是 DoubleBuffered + BeginUpdate/EndUpdate + PID 追蹤選取。現代化可改用 System.Management WMI 查詢或 PerformanceCounter 取得更豐富指標。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved