«Как вы проводите время на компьютере?»

Я сделал приложение Windows Forms для отслеживания всех процессов, запущенных на моей машине, и это также экономит время, когда приложение «активно», активным приложением является тот, который сейчас находится в фокусе, например, ваш браузер прямо сейчас + это также напоминает мне время от времени (каждый час), сколько времени я провел в Интернете.

Кроме того, он показывает все ваши запущенные процессы с некоторой базовой информацией о них, есть также несколько различных параметров сортировки и восходящий /нисходящий порядок.

Вот как это выглядит:

 введите описание изображения здесь>> </a>
<Сильный> Обновление </STRONG> </p>

<p> Эта строка -  winforms не является мощной , похоже, вызвала довольно противоречие и, видимо, я ошибаюсь. Как указано <a href= CodyGray и подтвержденный t3chb0t , формы окон могут быть очень быстрыми, если программа оптимизирована должным образом и используются элементы управления как они предназначены для использования.

Он работает на одном потоке , а winforms не является мощным , поэтому для обновления содержимого, которое происходит каждые 10 секунд, требуется 1-2 секунды, если только не запрошено вручную.

Вот основной код:

public partial class Form1 : Form
{
    private class ProcessInfo
    {
        public Process Process { get; }
        public TimeSpan TimeActive { get; set; }

        public ProcessInfo(Process process, TimeSpan timeActive)
        {
            Process = process;
            TimeActive = timeActive;
        }
    }

    private readonly Timer updateTimer = new Timer();
    private readonly Timer focusTimeTimer = new Timer();

    private Dictionary<int, Process> processesInfo = new Dictionary<int, Process>();
    private List<KeyValuePair<int, Process>> orderedProcessesInfo;

    private Dictionary<string, Action> sortingActions;

    private Dictionary<string, Action> orderingActions;

    private bool isAscendingOrder = false;

    private static Dictionary<int, ProcessInfo> processesActiveTime = new Dictionary<int, ProcessInfo>();

    private static readonly Func<Process, int> GetMemoryUsageInMB = p => (int) (p.WorkingSet64 / (1024 * 1024));
    private static readonly Func<Process, TimeSpan> GetRuntimeOfProcess = p => DateTime.Now - p.StartTime;
    private static readonly Func<Process, TimeSpan> GetActiveTimeOfProcess = p => processesActiveTime[p.Id].TimeActive;

    //save state after update
    private string lastSortAction = string.Empty;

    public Form1()
    {
        InitializeComponent();
        LoadProcesses();
        InitializeSortingActions();
        InitializeOrderingActions();
        UpdateProcessList();

        updateTimer.Interval = 1000 * 10;
        updateTimer.Tick += UpdateTimer_Tick;
        updateTimer.Start();

        focusTimeTimer.Interval = 1000;
        focusTimeTimer.Tick += FocusTimeTimer_Tick;
        focusTimeTimer.Start();
    }

    private void FocusTimeTimer_Tick(object sender, EventArgs e)
    {
        TextBoxProcessCount.Text = processesInfo.Count.ToString();
        IntPtr activatedHandle = GetForegroundWindow();
        if (activatedHandle == IntPtr.Zero)
        {
            return;
        }
        int activeProcessId;
        GetWindowThreadProcessId(activatedHandle, out activeProcessId);
        ProcessInfo activeProcess;
        if (processesActiveTime.TryGetValue(activeProcessId, out activeProcess))
        {
            activeProcess.TimeActive =
                activeProcess.TimeActive.Add(new TimeSpan(0, 0, focusTimeTimer.Interval / 1000));
            if (activeProcess.TimeActive.Seconds == 0 && activeProcess.TimeActive.Minutes == 0 &&
                activeProcess.TimeActive.TotalHours > 0)
            {
                MessageBox.Show(
                    [email protected]"You've spent {activeProcess.TimeActive.TotalHours} on {activeProcess.Process.ProcessName}");
            }
        }
        else
        {
            LoadProcesses();
            UpdateProcessList();
        }
    }

    private void LoadProcesses()
    {
        if (processesActiveTime.Count > 0)
        {
            try
            {
                processesActiveTime =
                    processesActiveTime.Where(p => !p.Value.Process.HasExited)
                        .ToDictionary(pair => pair.Key, pair => pair.Value);
            }
            catch (InvalidOperationException) { }
        }

        processesInfo.Clear();
        Process[] allProcesses = Process.GetProcesses();
        foreach (var process in allProcesses)
        {
            try
            {
                //ensures process wont deny access
                if (!process.HasExited)
                {
                    DateTime runtime = process.StartTime;
                }
            }
            catch (Win32Exception)
            {
                continue;
            }
            try
            {
                //ensures process wont exit
                processesInfo.Add(process.Id, process);
                if (!processesActiveTime.ContainsKey(process.Id))
                {
                    processesActiveTime.Add(process.Id, new ProcessInfo(process, new TimeSpan()));
                }
            }
            catch (InvalidOperationException) { }
        }
        orderedProcessesInfo = processesInfo.ToList();
    }

    private void InitializeSortingActions()
    {
        sortingActions = new Dictionary<string, Action>
        {
            ["Name"] = () => SortProcesses(p => p.ProcessName),
            ["Status"] = () => SortProcesses(p => p.Responding),
            ["Start Time"] = () => SortProcesses(p => p.StartTime),
            ["Total Runtime"] = () => SortProcesses(p => GetRuntimeOfProcess(p)),
            ["Memory Usage"] = () => SortProcesses(p => GetMemoryUsageInMB(p)),
            ["Active Time"] = () => SortProcesses(p => GetActiveTimeOfProcess(p))
        };

        foreach (var sortingAction in sortingActions)
        {
            ComboBoxSorting.Items.Add(sortingAction.Key);
        }
    }

    private void InitializeOrderingActions()
    {
        orderingActions = new Dictionary<string, Action>
        {
            ["Ascending"] = () =>
            {
                isAscendingOrder = true;
                if (!string.IsNullOrEmpty(lastSortAction))
                {
                    sortingActions[lastSortAction].Invoke();
                }
            },
            ["Descending"] = () =>
            {
                isAscendingOrder = false;
                if (!string.IsNullOrEmpty(lastSortAction))
                {
                    sortingActions[lastSortAction].Invoke();
                }
            },
        };
        foreach (var orderingAction in orderingActions)
        {
            ComboBoxOrders.Items.Add(orderingAction.Key);
        }
    }

    private void SortProcesses<T>(Expression<Func<Process, T>> lambda)
        where T : IComparable
    {
        orderedProcessesInfo.RemoveAll(p => p.Value.HasExited);

        orderedProcessesInfo.Sort(
            (process1, process2) =>
                lambda.Compile()
                    .Invoke(process1.Value).CompareTo(lambda.Compile()
                        .Invoke(process2.Value)));
        if (isAscendingOrder)
        {
            orderedProcessesInfo.Reverse();
        }
        processesInfo = orderedProcessesInfo.ToDictionary(pair => pair.Key, pair => pair.Value);

        UpdateProcessList();
    }

    private void UpdateTimer_Tick(object sender, EventArgs e)
    {
        RefreshList();
    }

    public void UpdateProcessList()
    {
        //refresh the timer's interval
        updateTimer.Stop();
        updateTimer.Start();

        ListViewProcesses.Clear();

        ListViewProcesses.Columns.Add("Name".ExtendWithEmptySpaces(GetAverageLengthOf(p => p.ProcessName.Length)));
        ListViewProcesses.Columns.Add("Status");
        ListViewProcesses.Columns.Add("Total Runtime");
        ListViewProcesses.Columns.Add("Active Runtime");
        ListViewProcesses.Columns.Add(
            "Start time".ExtendWithEmptySpaces(GetAverageLengthOf(p => p.StartTime.ToString().Length)));
        ListViewProcesses.Columns.Add(
            "Memory Usage".ExtendWithEmptySpaces(GetAverageLengthOf(p => GetMemoryUsageInMB(p).ToString().Length)));
        ListViewProcesses.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
        ListViewProcesses.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);

        foreach (var processInfo in processesInfo)
        {
            TimeSpan runtime = GetRuntimeOfProcess(processInfo.Value);
            TimeSpan activeTime = GetActiveTimeOfProcess(processInfo.Value);
            ListViewProcesses.Items.Add(
                CreateListViewRow(
                    name: processInfo.Value.ProcessName,
                    status: processInfo.Value.Responding ? "Active" : "Not responding",
                    runtime: $"{(int) runtime.TotalHours} h : {runtime.Minutes} min",
                    activeTime: $"{(int) activeTime.TotalHours} h : {activeTime.Minutes} min",
                    startTime: processInfo.Value.StartTime.ToString("g"),
                    memoryUsage: GetMemoryUsageInMB(processInfo.Value) + " MB"));
        }
    }

    private void bUpdate_Click(object sender, EventArgs e)
    {
        RefreshList();
    }

    private void RefreshList()
    {
        LoadProcesses();
        if (!string.IsNullOrEmpty(lastSortAction))
        {
            sortingActions[lastSortAction].Invoke();
        }
        else
        {
            UpdateProcessList();
        }
    }

    private static ListViewItem CreateListViewRow(string name, string status, string runtime, string activeTime,
        string startTime, string memoryUsage)
        => new ListViewItem(new[] {name, status, runtime, activeTime, startTime, memoryUsage});

    private int GetAverageLengthOf(Func<Process, int> predicate)
        => (int) Math.Ceiling(processesInfo.Values.Where(p => !p.HasExited).Average(predicate.Invoke));

    private void ComboBoxSorting_SelectedIndexChanged(object sender, EventArgs e)
    {
        lastSortAction = ((Control) sender).Text;
        sortingActions[lastSortAction].Invoke();
    }

    private void ComboBoxOrders_SelectedIndexChanged(object sender, EventArgs e)
    {
        orderingActions[((Control)sender).Text].Invoke();
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
}

Здесь всего один метод расширения, который помогает мне выравнивать ширину столбцов, поэтому они не выглядят пустыми, но они также не скрывают слишком много контента:

public static class Extensions
{
    public static string ExtendWithEmptySpaces(this string mainString, int desiredLength)
    {
        if (mainString.Length == desiredLength)
        {
            return mainString;
        }
        StringBuilder extendedStringBuilder = new StringBuilder(mainString);
        for (int i = 0; i < desiredLength - mainString.Length; i++)
        {
            extendedStringBuilder.Append(" ");
        }
        return extendedStringBuilder.ToString();
    }
}

Не стесняйтесь использовать его, но имейте в виду, что нет способа записать, сколько времени вы потратили на приложения, если программа не открыта.

36 голосов | спросил Denis 8 Jam1000000amSun, 08 Jan 2017 07:00:13 +030017 2017, 07:00:13

3 ответа


49

Производительность

  

[..] winforms не является мощным, поэтому для обновления содержимого требуется 1-2 секунды [..]

Это не WinForms, потому что это на самом деле очень быстро, и у меня никогда не было никаких проблем с ним. Есть вероятность 99,99%, что код неэффективен, поэтому давайте посмотрим.

Expression.Compile ()

Это то, что замедляет приложение и где шея бутылки скрыта и где Expression s bite.

private void SortProcesses<T>(Expression<Func<Process, T>> lambda)
  where T : IComparable
{
  orderedProcessesInfo.RemoveAll(p => p.Value.HasExited);

  orderedProcessesInfo.Sort(
      (process1, process2) =>
          lambda.Compile()
              .Invoke(process1.Value).CompareTo(lambda.Compile()
                  .Invoke(process2.Value)));
  if (isAscendingOrder)
  {
      orderedProcessesInfo.Reverse();
  }
  processesInfo = orderedProcessesInfo.ToDictionary(pair => pair.Key, pair => pair.Value);

  UpdateProcessList();
}

Есть два (!) Compile s. Они очень дороги и не нужны им и Expression, потому что ничего динамически не меняется. У вас всегда есть Process и значение, которое нужно получить и сравнить. Используйте только Func:

private void SortProcesses<T>(Func<Process, T> getProperty)
    where T : IComparable
{

    // ...

    orderedProcessesInfo.Sort((process1, process2) =>
            getProperty(process1.Value)
            .CompareTo(getProperty(process2.Value))
    );

    // ...
}

Это метод сортировки, и он должен работать быстро. Если это не так, вы сразу заметите это.


Вот ссылка на ответ Lippert @ Eric на Stack Overflow, объясняющий, что делает метод Compile: Что делает Метод Lambda Expression Compile () делает? .

И еще одна ссылка на другой ответ (кто-то другой) на Stack Overflow, сравнивающий время выполнения различных вызовов методов: Производительность выражения. Компиляция vs Lambda, прямые виртуальные вызовы


Другая вещь, которая мне не нравится в этом, - это

orderedProcessesInfo.Reverse()

Sort уже должен содержать правильный порядок. Вышеприведенная строка выглядит так: сортировка не будет работать правильно, и вам понадобится этот обходной путь для fix вместо фиксации функции сортировки.


ListView.Items.Add ()

Второй способ, который, скорее всего, заставляет вас думать, что WinForms не будет работать хорошо, это одно:

public void UpdateProcessList()

Вы звоните здесь

ListViewProcesses.Clear();
     

[..] этот метод удаляет все элементы и столбцы из элемента управления ListView

только для немедленного воссоздания столбцов с помощью

ListViewProcesses.Columns.Add(..);

Вы действительно хотите это делать каждый раз, когда обновляете список? Вы уже создали список-просмотр один раз. Я предполагаю, что вы действительно хотели бы просто удалить все элементы с помощью ListView.Items.Clear().

Это и добавление многих строк в список в цикле без приостановки, это действительно повредило производительность, потому что просмотр списка продолжает обновляться после каждого изменения.

foreach (var processInfo in processesInfo)
{     
    // ..
    ListViewProcesses.Items.Add(..);
    // ..
}

Рассмотрите использование методов BeginUpdate и EndUpdate и добавьте строки между ними или лучше, используйте метод AddRange:

  

Предпочтительным способом добавления нескольких элементов в ListView является использование метода AddRange в ListView.ListViewItemCollection (доступ через свойство Items ListView). Это позволяет вам добавить массив элементов в список за одну операцию. Однако, если вы хотите добавлять элементы по одному с помощью метода Добавить из класса ListView.ListViewItemCollection, вы можете использовать метод BeginUpdate, чтобы предотвратить перекраску элемента управления ListView при каждом добавлении элемента. Когда вы выполнили задачу добавления элементов в элемент управления, вызовите метод EndUpdate, чтобы разрешить перерисовку ListView. Этот способ добавления элементов может предотвратить мерцающий рисунок ListView, когда к элементу управления добавляется множество элементов.

Метод ListView.BeginUpdate


Для дальнейшего повышения производительности вы можете попытаться получить свой собственный элемент списка-списка издалее

public class ListViewItem

, который, к счастью, не запечатан. Затем вместо повторного добавления всех элементов вы можете просто обновить элементы, и в представлении списка можно просто обновить значения с помощью ListView.Refresh(). Чтобы отслеживать элементы и процессы в виде списка, вы можете использовать другой dictionaray, и если новый процесс будет добавлен или удален, вы добавите /удалите только этот, а не все из них.


Дизайн

private Dictionary<string, Action> sortingActions;

private Dictionary<string, Action> orderingActions;

private bool isAscendingOrder = false;

Я нахожу эти три поля очень запутанными, потому что они звучат так похожи.

Как насчет этого. Сначала переименуйте этот

sortingActions -> selectColumn

С новой настройкой вы выберете столбец (значение), еще не вызвав сортировку:

private Dictionary<string, Func<Process, IComparable>> selectColumn;

selectColumn = new Dictionary<string, Func<Process, IComparable>>
{
    ["Name"] = p => p.ProcessName,
    ["Status"] = p => p.Responding,
    ["Start Time"] = p => p.StartTime,
    ["Total Runtime"] = p => GetRuntimeOfProcess(p),
    ["Memory Usage"] = p => GetMemoryUsageInMB(p),
    ["Active Time"] = p => GetActiveTimeOfProcess(p)
};

Затем переименуйте другой словарь в

orderingActions -> orderBy

, где ключ больше не является строкой, а перечисляет:

enum OrderBy
{
    Ascending,
    Descending
}

, поэтому новый словарь теперь имеет более сильные клавиши, а его элементы запускают функцию сортировки, используя первый словарь для получения делегата для получения столбца (значения)

orderBy = new Dictionary<OrderBy, Action>
{
    [OrderBy.Ascending] = () =>
    {
        SortProcesses(selectColumn[orderByColumn]);
        currentOrderBy = OrderBy.Ascending;
    },
    [..]
}

, где orderByColumn - это имя столбца, который нужно упорядочить, тем, что вы где-то установили.

isAscendingOrder теперь становится currentOrderBy

private OrderBy currentOrderBy = OrderBy.Descending;

(отказ от ответственности: кодирование блокнота, возможно, еще не на 100%)

ответил t3chb0t 8 Jam1000000amSun, 08 Jan 2017 09:23:39 +030017 2017, 09:23:39
15

Первое, что я хотел бы сделать, это изменить if (mainString.Length == desiredLength) на if (mainString.Length >= desiredLength), так как, если он длиннее, 'просто сделайте избыточную работу с StringBuilder, и вы также можете вернуться на ранней стадии на всех условиях, которые удовлетворяли бы его.


Я бы также объединил этот блок try /catch:

try
{
    //ensures process wont deny access
    if (!process.HasExited)
    {
        DateTime runtime = process.StartTime;
    }
}
catch (Win32Exception)
{
    continue;
}
try
{
    //ensures process wont exit
    processesInfo.Add(process.Id, process);
    if (!processesActiveTime.ContainsKey(process.Id))
    {
        processesActiveTime.Add(process.Id, new ProcessInfo(process, new TimeSpan()));
    }
}
catch (InvalidOperationException) { }

To:

try
{
    //ensures process wont deny access
    if (!process.HasExited)
    {
        DateTime runtime = process.StartTime;
    }

    //ensures process wont exit
    processesInfo.Add(process.Id, process);
    if (!processesActiveTime.ContainsKey(process.Id))
    {
        processesActiveTime.Add(process.Id, new ProcessInfo(process, new TimeSpan()));
    }
}
catch (Win32Exception) { continue; }
catch (InvalidOperationException) { }

С экспрессируемыми членами, я нашел, что читать легче, когда синтаксис лямбда находится в той же строке, что и член.

private static ListViewItem CreateListViewRow(string name, string status, string runtime, string activeTime,
    string startTime, string memoryUsage)
    => new ListViewItem(new[] {name, status, runtime, activeTime, startTime, memoryUsage});

To:

private static ListViewItem CreateListViewRow(string name, string status, string runtime, string activeTime,
    string startTime, string memoryUsage) =>
    new ListViewItem(new[] {name, status, runtime, activeTime, startTime, memoryUsage});

Далее, тот же самый блок сверху:

private static ListViewItem CreateListViewRow(string name, string status, string runtime, string activeTime,
    string startTime, string memoryUsage) =>
    new ListViewItem(new[] {name, status, runtime, activeTime, startTime, memoryUsage});

Перерыв каждый параметр в новой строке, если вы собираетесь сломать один из них:

private static ListViewItem CreateListViewRow(string name,
                                              string status,
                                              string runtime
                                              string activeTime,
                                              string startTime,
                                              string memoryUsage) =>
    new ListViewItem(new[] {name, status, runtime, activeTime, startTime, memoryUsage});

Как только мы это сделаем, мы видим, что это немного уродливо, этот метод не является местом для элементов с выраженным выражением:

private static ListViewItem CreateListViewRow(string name,
                                              string status,
                                              string runtime
                                              string activeTime,
                                              string startTime,
                                              string memoryUsage)
{
    return new ListViewItem(new[]
    {
        name,
        status,
        runtime,
        activeTime,
        startTime, 
        memoryUsage
    });
}

Я постараюсь добавить более позднее, довольно поздно прямо сейчас, и, вероятно, еще многое можно сказать.

ответил 202_accepted 8 Jam1000000amSun, 08 Jan 2017 07:12:18 +030017 2017, 07:12:18
8
  

Я добавил несколько строк в список и попытался обновить его несколько раз, чтобы посмотреть, как это выглядит. И угадайте, что это было мерцание, и потребовалось около 100 мс, чтобы обновить представление списка. Который я считаю медленным, это всего лишь 4-5 обновленных пустых строк, вы не можете так быстро позвонить. Попытайтесь положить много элементов управления на свою форму, и в конце концов он откажется. Худшая часть о формах окон - это, вероятно, чертеж элементов управления.

Правда, это было бы ужасно, так что вот еще один быстрый & грязный пример ListView обновляется с 100 элементами каждые 0,5 сек., а каждое значение обновляется каждые 50 мс. Никаких мерцаний, никаких задержек. Это быстро, черт возьми, и должно быть. Если это не так, то что-то не так.

Это можно запустить в LINQPad:

void Main()
{
    var form = new Form();

    var lv = new MyListView
    {
        Dock = DockStyle.Fill
    };
    lv.Columns.Add("Column1");
    lv.Columns.Add("Column2");
    lv.Columns.Add("Column3");
    lv.View = View.Details;
    form.Controls.Add(lv);
    lv.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);

    var itemsTimer = new System.Windows.Forms.Timer
    {
        Interval = 500,
    };

    var suspendLayout = true;
    var itemCount = 100;

    var valueTimer = new System.Windows.Forms.Timer
    {
        Interval = 50
    };

    itemsTimer.Tick += (sender, e) =>
    {
        valueTimer.Stop();
        if (suspendLayout) lv.BeginUpdate();
        lv.Items.Clear();
        for (int i = 0; i < itemCount; i++)
        {
            lv.Items.Add(new ListViewItem(new string[] { "Foo", "Bar", "Baz" }));
        }
        if (suspendLayout) lv.EndUpdate();
        valueTimer.Start();
    };
    itemsTimer.Start();


    var rnd = new Random();
    valueTimer.Tick += (sender, e) =>
    {
        if (suspendLayout) lv.BeginUpdate();
        for (int i = 0; i < itemCount; i++)
        {
            lv.Items[i].SubItems[rnd.Next(0, 3)].Text = rnd.Next(0, 10).ToString();
        }
        if (suspendLayout) lv.EndUpdate();
    };

    form.FormClosing += (sender, e) =>
    {
        valueTimer.Stop();
        itemsTimer.Stop();
    };

    form.Show();
}

class MyListView : ListView
{
    public MyListView() { DoubleBuffered = true; }
    protected override bool DoubleBuffered { get; set; }
}

ListView «> </a> </p></body></html>

ответил t3chb0t 8 Jpm1000000pmSun, 08 Jan 2017 17:40:43 +030017 2017, 17:40:43

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132