I am new to TPL and I am trying to test a very simple application using parallelism.
I am using a WinForms, C#, VS2015
In my form, I have one progress bar, a timer and a gauge. I am using Infragistics 15.2 controls.
PerformanceCounter
timer.Tick
(I set a 500ms interval), I read the CPU usage from the PerformanceCounter and update the value of the Gauge control. My problem is that the first access to NextValue() of the CPU counter is really time expensive and freezes the UI. I was expecting to have a full responsive UI but still it freezes. I am missing something for sure, but I cannot find out what. I am pretty sure that The blocking action is the NextValue()
: if I replace it with a random number generator, my UI is fully responsive.
Can you please help me?
public partial class Form1 : Form
{
PerformanceCounter _CPUCounter;
public Form1()
{
InitializeComponent();
}
private async void ultraButton1_Click(object sender, EventArgs e)
{
var progress = new Progress<int>(valuePBar => {ultraProgressBar1.Value = valuePBar; });
await Task.Run(() => UpdatePBar(progress));
}
private void Form1_Load(object sender, EventArgs e)
{
Task.Run(() =>
{
_CPUCounter = new PerformanceCounter();
_CPUCounter.CategoryName = "Processor";
_CPUCounter.CounterName = "% Processor Time";
_CPUCounter.InstanceName = "_Total";
BeginInvoke(new Action(() =>
{
timer1.Interval = 500;
timer1.Start();
}));
});
}
private async void timer1_Tick(object sender, EventArgs e)
{
float usage = await Task.Run(() => _CPUCounter.NextValue());
RadialGauge r_gauge = (RadialGauge)ultraGauge1.Gauges[0];
r_gauge.Scales[0].Markers[0].Value = usage;
}
public void UpdatePBar(IProgress<int> progress)
{
for (Int32 seconds = 1; seconds <= 10; seconds++)
{
Thread.Sleep(1000); //simulate Work, do something with the data received
if (seconds != 10 && progress != null)
{
progress.Report(seconds * 10);
}
}
}
}
You should:
Task.Run
over Task.Factory.StartNew
. async
/ await
if you want to "return to the UI thread". IProgress<T>
for progress updates. TaskScheduler
s or SynchronizationContext
s if absolutely necessary (and they're not necessary here). Control.BeginInvoke
or Control.Invoke
. In this case, your timer can just run NextValue
on a thread pool thread using Task.Run
, and then update the UI with the resulting value:
private async void OnTimerTickElapsed(Object sender, EventArgs e)
{
float usage = await Task.Run(() => _CPUCounter.NextValue());
RadialGauge r_gauge = (RadialGauge)this.ultraGauge.Gauges[0];
r_gauge.Scales[0].Markers[0].Value = usage;
}
For your progress bar, have your MyMethod
take an IProgress<int>
:
private void ButtonClick(Object sender, EventArgs e)
{
var progress = new Progress<int>(valuePBar => { this.progressBar.Value = valuePBar; });
MyMethod(progress);
}
MyMethod(IProgress<int> progress)
{
...
progress.Report(50);
...
}
Three things...
Task.Run(() => { // progress bar code }
CPUCounter
is not static
_CPUCounter.NextValue()
method supports async and is awaited, otherwise it will block. I would also favor Task.Run
over Task.Factory.StartNew
as it has better defaults (it calls Task.Factory.StartNew
internally)
private void OnTimerTickElapsed(Object sender, EventArgs e)
{
await Task.Run(async () =>
{
float usage = await _CPUCounter.NextValue();
RadialGauge r_gauge = (RadialGauge)this.ultraGauge.Gauges[0];
r_gauge.Scales[0].Markers[0].Value = usage;
}, TaskCreationOptions.LongRunning);
}
Have a look at the interval you're using. 50ms is a tad bit fast for the overhead of TPL.
Tried your code (just replaced the gauge with a simple label showing the usage
variable and of course didn't click the button because you didn't provide MyMethod
) and didn't experience any UI blocking.
Posting the correct code for updating the UI (using Task
for that purpose even with UI scheduler IMO is a overkill):
private void UpdateProgressBar(Int32 valuePBar)
{
BeginInvoke(new Action(() =>
{
this.progressBar.Value = valuePBar;
}));
}
private void OnTimerTickElapsed(Object sender, EventArgs e)
{
Task.Run(() =>
{
float usage = _CPUCounter.NextValue();
BeginInvoke(new Action(() =>
{
RadialGauge r_gauge = (RadialGauge)this.ultraGauge.Gauges[0];
r_gauge.Scales[0].Markers[0].Value = usage;
}));
});
}
Still, there is no visible reason in your code that can cause UI blocking. Except eventually this part
_CPUCounter = new PerformanceCounter();
_CPUCounter.CategoryName = "Processor";
_CPUCounter.CounterName = "% Processor Time";
_CPUCounter.InstanceName = "_Total";
which runs on the UI thread in your OnFormLoad
. You can try moving that code on a separate task like this
private void OnFormLoad(Object sender, EventArgs e)
{
Task.Run(() =>
{
_CPUCounter = new PerformanceCounter();
_CPUCounter.CategoryName = "Processor";
_CPUCounter.CounterName = "% Processor Time";
_CPUCounter.InstanceName = "_Total";
BeginInvoke(new Action(() =>
{
this.timer.Interval = 500;
this.timer.Start();
}));
});
}
Somebody has already investigated this problem
The problem is that PerformanceCounter initialization is complex and takes a lot of time. What is not clear though is that whether this initialization block all threads or just the owning thread(the thread that creates the PerformanceCounter).
But I think your code already provided the answer. Since you are already creating the PerformanceCounter in a thread pool thread and it still blocks the UI. Then it is probably correct to assume the initialization blocks all threads.
If the initialization blocks all threads, then the solution would be to initialize the PerformanceCounter on application startup. Either by not using the default constructor(pick a constructor that not only constructs the instance but also initializes it) or call NextValue once during startup.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.