0

I've been attempting to implement a CPU monitor that updates every two seconds and displays it on a label called "cpu_usage" in WinForms. Unfortunately my code does not appear to work and gives off this error during runtime:

 System.InvalidOperationException: 'Cross-thread operation not valid: Control 'cpu_usage' accessed from a thread other than the thread it was created on.'

So far I've done a little bit of debugging, and have out that the error occurs whenever I try to display the percentage on the "cpu-usage" label, but I am still unable to figure out how to fix this issue. The CPU monitoring code is below:

    public my_form()
    {
        InitializeComponent();

        // Loads the CPU monitor
        cpuCounter = new PerformanceCounter();
        cpuCounter.CategoryName = "Processor";
        cpuCounter.CounterName = "% Processor Time";
        cpuCounter.InstanceName = "_Total";
        InitTimer();
    }

    // Timer for the CPU percentage check routine
    public void InitTimer()
    {
        cpu_timer = new Timer();
        cpu_timer.Elapsed += new ElapsedEventHandler(cpu_timer_Tick);
        cpu_timer.Interval = 2000;
        cpu_timer.Start();
    }

    // Initates the checking routine
    private void cpu_timer_Tick(object sender, EventArgs e)
    {
        cpu_usage.Text = getCurrentCpuUsage(); // This line causes the exception error.
    }

    // Method to find the CPU resources
    public string getCurrentCpuUsage()
    {
        string value1 = (int)cpuCounter.NextValue() + "%";
        Thread.Sleep(500);
        string value2 = (int)cpuCounter.NextValue() + "%";
        return value2.ToString();
    }
OKprogrammer
  • 147
  • 1
  • 7
  • 1
    Though you didn't show the usings, judging from `ElapsedEventHandler` you use `System.Timers.Timer` instead of `System.Windows.Forms.Timer`. The latter invokes its `Tick` event from the UI thread so it is safe to access `cpu_usage.Text` from its handler. – György Kőszeg Sep 04 '21 at 18:26
  • Does this answer your question? [Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on](https://stackoverflow.com/questions/142003/cross-thread-operation-not-valid-control-accessed-from-a-thread-other-than-the) – Dmitry Sep 04 '21 at 18:31
  • @GyörgyKőszeg Ok, I will try to use `System.Windows.Forms.Timer` and add it to my question. – OKprogrammer Sep 04 '21 at 18:33
  • Even if you switch to the Forms Timer, you need to get rid of that call to Sleep() then. Use await/async to fix that. – Idle_Mind Sep 04 '21 at 18:45
  • @Idle_Mind Yes. I just fixed the error and it appears that piece of code is causing a freeze in the UI. – OKprogrammer Sep 04 '21 at 18:50
  • Really, though, what is the purpose of that Sleep()? The first result in value1 isn't even being used. – Idle_Mind Sep 04 '21 at 19:00
  • Yeah, like György said, with Forms.Timer your Tick event now runs on the UI thread. So if you call Thread.Sleep you are letting the UI thread sleep, which means it can neither draw anything nor accept inputs. That’s why Idle_Mind said that you need to replace the Thread.Sleep with `await Task.Delay` which doesn’t stop the UI thread. In general the latter should always be preferred over the former. `getCurrentCpuUsage` must be declared as `async Task` so that await works. And `cpu_timer_tick` as `async void` and await your call to `getCurrentCpuUsage`. – ckuri Sep 04 '21 at 19:24

2 Answers2

1

I managed to fix this error by using System.Windows.Forms for the timer, instead of using the System.Timers.Timer namespace. Additionally, I changed my code to use await and async, to make sure that the thread running the user interface is not frozen during the update. The new code is below:

    // Timer for the CPU percentage check routine
    public void InitTimer()
    {
        cpu_timer.Tick += new EventHandler(cpu_timer_Tick);
        cpu_timer.Interval = 2000; // in miliseconds
        cpu_timer.Start();
    }

    // Initates the checking routine
    private async void cpu_timer_Tick(object sender, EventArgs e)
    {
        Task<string> cpu_task = new Task<string>(getCurrentCpuUsage);
        cpu_task.Start();
        cpu_usage.Text = await cpu_task;
    }
OKprogrammer
  • 147
  • 1
  • 7
  • It should be `Task cpu_task = Task.Run(getCurrentCpuUsage);`. Using the Task constructor to create a cold task and manually starting it, isn’t recommended. – ckuri Sep 05 '21 at 10:02
0

Like others are saying, I believe you want to execute the setting of the text on the UI thread... can try something like this:

    // Initates the checking routine
    private void cpu_timer_Tick(object sender, EventArgs e)
    {
        cpu_usage.Invoke((MethodInvoker)delegate {
            // Running on the UI thread
            cpu_usage.Text = getCurrentCpuUsage();
        });
    }
jimnkey
  • 372
  • 2
  • 10