Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Long running task is blocking the UI

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.

  • the button will launch a function which does some work
  • Form Load will start a Windows.Form.Timer and instantiate the PerformanceCounter
  • On 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);
            }
        }
    }
}
like image 875
Valentina Avatar asked Dec 01 '25 02:12

Valentina


1 Answers

You should:

  • Never update UI controls from background threads.
  • Prefer Task.Run over Task.Factory.StartNew.
  • Prefer async/await if you want to "return to the UI thread".
  • Use IProgress<T> for progress updates.
  • Only use TaskSchedulers or SynchronizationContexts if absolutely necessary (and they're not necessary here).
  • Never use 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);
  ...
}
like image 136
Stephen Cleary Avatar answered Dec 02 '25 15:12

Stephen Cleary



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!