patterncsharpCritical
"How are you spending your time on the computer?"
Viewed 0 times
theyouryouarespendingtimecomputerhow
Problem
I've made a Windows Forms application to track all of the processes running on my machine and it also saves the time an application is "active", an active application is the one that is on focus currently e.g your browser right now + it also reminds me every now and then (every hour) how much time I've spent on the internet.
Aside from that it shows all of your running processes with some basic information about them, there are also several different sorting options along with ascending/descending ordering.
Here's how it looks:
Update
This line - winforms is not powerful seems to have caused quite some controversy and apparently I'm wrong. As pointed by CodyGray and proven by t3chb0t, windows forms can be really fast if the program is optimized properly and the controls are used the way they are meant to be used.
It works on a single thread and winforms is not powerful so it takes 1-2 seconds to refresh the content which happens every 10 seconds, unless requested manually.
Here is the main code:
```
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 processesInfo = new Dictionary();
private List> orderedProcessesInfo;
private Dictionary sortingActions;
private Dictionary orderingActions;
private bool isAscendingOrder = false;
private static Dictionary processesActiveTime = new Dictionary();
private static readonly Func GetMemoryUsageInMB = p => (int) (p.WorkingSet64 / (1024 * 1024));
private static readonly Func GetRuntimeOfProcess = p => DateTime.Now - p.StartTime;
private static readonly Func GetActiveTimeOfProcess = p =
Aside from that it shows all of your running processes with some basic information about them, there are also several different sorting options along with ascending/descending ordering.
Here's how it looks:
Update
This line - winforms is not powerful seems to have caused quite some controversy and apparently I'm wrong. As pointed by CodyGray and proven by t3chb0t, windows forms can be really fast if the program is optimized properly and the controls are used the way they are meant to be used.
It works on a single thread and winforms is not powerful so it takes 1-2 seconds to refresh the content which happens every 10 seconds, unless requested manually.
Here is the main code:
```
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 processesInfo = new Dictionary();
private List> orderedProcessesInfo;
private Dictionary sortingActions;
private Dictionary orderingActions;
private bool isAscendingOrder = false;
private static Dictionary processesActiveTime = new Dictionary();
private static readonly Func GetMemoryUsageInMB = p => (int) (p.WorkingSet64 / (1024 * 1024));
private static readonly Func GetRuntimeOfProcess = p => DateTime.Now - p.StartTime;
private static readonly Func GetActiveTimeOfProcess = p =
Solution
Performance
[..] winforms is not powerful so it takes 1-2 seconds to refresh the content [..]
It's not WinForms because it's actually very fast and I have never had any issues with it. There is a 99.99% chance that the code is inefficient so let's have a look.
Expression.Compile()
This is what slows the application down and where the bottle-neck is hidden and where the
There are two (!)
This is a sort method and it needs to run fast. If it doesn't, you'll notice it right away.
Here's a link to @Eric's Lippert answer on Stack Overflow explaining what the
And one more link to another answer (by someone else) on Stack Overflow comparing execution times of various method calls: Performance of Expression.Compile vs Lambda, direct vs virtual calls
Another thing that I don't like about it is this
The
ListView.Items.Add()
The second method that most likely makes you think WinForms wouldn't be performing well is this one:
You call here
[..] this method removes all items and columns from the ListView control
only to immediately recreate the columns with
Do you really want to do it each time you refresh the list? You have already created the list-view once. I guess what you really wanted to do is to just remove all the items with
This and adding the many rows to the list in a loop without suspending it really hurt the performance because the list-view keeps refreshing after each change.
Consider using the
The preferred way to add multiple items to a ListView is to use the AddRange method of the ListView.ListViewItemCollection (accessed through the Items property of the ListView). This enables you to add an array of items to the list in a single operation. However, if you want to add items one at a time using the Add method of the ListView.ListViewItemCollection class, you can use the BeginUpdate method to prevent the control from repainting the ListView every time that an item is added. When you have completed the task of adding items to the control, call the EndUpdate method to enable the ListView to repaint. This way of adding items can prevent flickered drawing of the ListView when lots of items are being added to the control.
ListView.BeginUpdate Method
To further improve the performance you could try to derive your own list-view-item from the
that luckily isn't sealed. Then instead of re-adding all items you could just refresh the items and the list-view could just update the values with
Design
I find these three fields very confusing because they sound so similar.
How about this. First rename this one
With the new defintion you selects the column (value) without invoking sorting yet:
```
private Dictionary> selectColumn;
selectColumn = new Dictionary>
{
["Name"] = p => p.ProcessName,
["Status"] = p => p.Responding,
["Start Time"] = p => p.StartTime,
["Total Runtime"] = p => GetRuntimeOfProcess(p),
["Me
[..] winforms is not powerful so it takes 1-2 seconds to refresh the content [..]
It's not WinForms because it's actually very fast and I have never had any issues with it. There is a 99.99% chance that the code is inefficient so let's have a look.
Expression.Compile()
This is what slows the application down and where the bottle-neck is hidden and where the
Expressions bite.private void SortProcesses(Expression> 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();
}There are two (!)
Compiles. They are very expensive and there is no need for them and the Expression because there is nothing that dynamically changes. You have always a Process and a value to get and to compare. Use just the Func:private void SortProcesses(Func getProperty)
where T : IComparable
{
// ...
orderedProcessesInfo.Sort((process1, process2) =>
getProperty(process1.Value)
.CompareTo(getProperty(process2.Value))
);
// ...
}This is a sort method and it needs to run fast. If it doesn't, you'll notice it right away.
Here's a link to @Eric's Lippert answer on Stack Overflow explaining what the
Compile method does: What does Lambda Expression Compile() method do?.And one more link to another answer (by someone else) on Stack Overflow comparing execution times of various method calls: Performance of Expression.Compile vs Lambda, direct vs virtual calls
Another thing that I don't like about it is this
orderedProcessesInfo.Reverse()The
Sort should already produce the right order. The above line looks like the sorting wouldn't work correctly and you need this workaround to fix it instead of fixing the sort function.ListView.Items.Add()
The second method that most likely makes you think WinForms wouldn't be performing well is this one:
public void UpdateProcessList()You call here
ListViewProcesses.Clear();[..] this method removes all items and columns from the ListView control
only to immediately recreate the columns with
ListViewProcesses.Columns.Add(..);Do you really want to do it each time you refresh the list? You have already created the list-view once. I guess what you really wanted to do is to just remove all the items with
ListView.Items.Clear().This and adding the many rows to the list in a loop without suspending it really hurt the performance because the list-view keeps refreshing after each change.
foreach (var processInfo in processesInfo)
{
// ..
ListViewProcesses.Items.Add(..);
// ..
}Consider using the
BeginUpdate and EndUpdate methods and adding the rows inbetween or better, use the AddRange method:The preferred way to add multiple items to a ListView is to use the AddRange method of the ListView.ListViewItemCollection (accessed through the Items property of the ListView). This enables you to add an array of items to the list in a single operation. However, if you want to add items one at a time using the Add method of the ListView.ListViewItemCollection class, you can use the BeginUpdate method to prevent the control from repainting the ListView every time that an item is added. When you have completed the task of adding items to the control, call the EndUpdate method to enable the ListView to repaint. This way of adding items can prevent flickered drawing of the ListView when lots of items are being added to the control.
ListView.BeginUpdate Method
To further improve the performance you could try to derive your own list-view-item from the
public class ListViewItemthat luckily isn't sealed. Then instead of re-adding all items you could just refresh the items and the list-view could just update the values with
ListView.Refresh(). To keep track of the list-view-items and processes you could use another dictionaray and if a new process is added or removed you add/remove just this one and not all of them.Design
private Dictionary sortingActions;
private Dictionary orderingActions;
private bool isAscendingOrder = false;I find these three fields very confusing because they sound so similar.
How about this. First rename this one
sortingActions -> selectColumnWith the new defintion you selects the column (value) without invoking sorting yet:
```
private Dictionary> selectColumn;
selectColumn = new Dictionary>
{
["Name"] = p => p.ProcessName,
["Status"] = p => p.Responding,
["Start Time"] = p => p.StartTime,
["Total Runtime"] = p => GetRuntimeOfProcess(p),
["Me
Code Snippets
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 SortProcesses<T>(Func<Process, T> getProperty)
where T : IComparable
{
// ...
orderedProcessesInfo.Sort((process1, process2) =>
getProperty(process1.Value)
.CompareTo(getProperty(process2.Value))
);
// ...
}orderedProcessesInfo.Reverse()public void UpdateProcessList()ListViewProcesses.Clear();Context
StackExchange Code Review Q#152018, answer score: 51
Revisions (0)
No revisions yet.