-1

I have literally no experience in threading, so bear with me, please. I'm making a monitoring/testing tool, that monitors hardware sensors and uses affinity masks and for loop to cycle through the cores one by one running a full-load single-core stress test. The problem is, that when the user starts the test, and affinity is set, instead of assigning just the test method to that core, it assigns the entire program, which means UI can run only on the core that is currently tested and is under full load. I guess it's clear that UI is just stalling during the test, and labels that output monitoring data are frozen, while it's crucial to have relevant readings during the test because this is the main purpose of this program.

After some research, I figured that I need to use threading but I never worked with it before. Here is my shortened code. The problem is, that when I use threading on any method that contains labels, it throws the error "Cross-thread operation not valid: Control 'CPUTempTDie' accessed from a thread other than the thread it was created on". I tried to start Report sensors in a new thread, but it doesn't help. I literally tried to start in a new thread every method and label that is involved, but it's either doesn't help or control score (score - is a result returned by TestMethod to compare it with the correct number to confirm stability) or the program just skips some part of the code and just says "Done".

The question is: Is it possible to set just a TestingMethod to a particular core, allowing the rest of the program (including UI) to use any other free core, and if not, what exactly should I start in a new thread to let UI update under the load?

//the method below updates labels and calls ReportSensors method that reads
//sensors on a timer tick

private void Monitoring()
{  
    sensor.ReportSensors(); //calls Method that reads sensors

    //Two labels below are stalling when TestingMethod runs

    CPUTempTDie.Value = (int)sensor.CpuTemp;
    FrequencyLabel.Text = sensor.CoreFrequency.ToString("0") + "MHz";  
}

private int TestingMethod()
{
    while (true)
    {
        //Performs calculations to generate load, returns the "score"
    }

    if (timer.Elapsed.TotalSeconds > 60)
    {
        break;
    }

    return score;
}


private async void PerCoreTest()
{
    try
    {
        await Task.Delay(3000);

        for (int i = 0; i < (numberOfCores); i++)
        {
            coreCounter++;

            Thread.BeginThreadAffinity();
            SetThreadAffinityMask(GetCurrentThread(), new IntPtr(intptrVal));

 //TestingMethod below being called twice, and results from both runs
            //are later compared for consistency. 

            TestingMethod();
            iter1 = score / 10000;

            TestingMethod();
            iter2 = score / 10000;


            maxScore = Math.Max(iter1, iter2);

            await Task.Delay(1000);

            TestLabel.Text = score.ToString();

            //Switches to the next thread mask
        }
    }
    finally
    {
        Thread.EndThreadAffinity();
    }
}



private void TestButton_Click(object sender, EventArgs e)
{
    using (Process p = Process.GetCurrentProcess())
        p.PriorityClass = ProcessPriorityClass.High;

    PerCoreTest();

    using (Process p = Process.GetCurrentProcess())
        p.PriorityClass = ProcessPriorityClass.Normal;
}

Clarification: My question was closed as a duplicate despite the linked thread doesn't answer my question. I ask to reopen it because:

  1. While "a large number of Remote Calls around 2000 - 3000 calls" mentioned in a linked thread might be heavy on some hardware, it's not the same as hammering the CPU with calculations in the while(true) loop, which squeeze all performance from any kind of hardware living nothing for UI if UI sits on the same core.

  2. Suggested solution in the thread that I allegedly duplicated doesn't resolve the issue, and my original question is completely different: I can not figure out what exactly must be put in a task to make UI run smoothly under the load.

  3. Suggestions from the comments under my thread don't answer the question too. I tried the solution from Panagiotis Kanavos (see below) but the problem persists:

while (true)
{
    await Task.Delay(500);
    await Task.Run(() => sesnor.ReportSensors()); 
}

After researching similar topics it seems like none of them address my particular issue.

New Coder
  • 45
  • 1
  • 5
  • 1
    [`BackgroundWorker`](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker) FTW. – Uwe Keim Jan 21 '22 at 16:33
  • Thank you for the response and thanks for editing. But I think it's important to mention in the title that this is a WindowsForms app because WinForms works with interface in a specific way, so this issue might not be relevant for those who use something else. – New Coder Jan 21 '22 at 16:37
  • 1
    You are going into a rabbit hole you are going to get lost in. As @UweKeim commented, `BackgroundWorker` is your friend, and please get rid of all those `Process.GetCurrentProcess()` calls – Camilo Terevinto Jan 21 '22 at 16:40
  • [You should _not_ include tags in a question's title](https://meta.stackexchange.com/a/130208/133056). Tags are the way to notify about a specific technology. – Uwe Keim Jan 21 '22 at 16:40
  • 6
    BackgroundWorker is the obsolete class used to execute single jobs in the background on Windows forms, back in .NET Framework 1-2. Since .NET Framework 4.x it's been completely replaced by `async/await`, `Task.Run` and `Progress` – Panagiotis Kanavos Jan 21 '22 at 16:41
  • 1
    It's unclear what exactly you want to execute in the background in all that code. If you want to just start something in the background, `Task.Run(()=>SomeMethod(someArgs));` is enough. If you want to do so in response to an event, change the event handler to `async void whatevet....` and use `await Task.Run(...);`. If you want to execute multiple such jobs one after the other, `var result1=await Task.Run(...); var result2=await Task.Run(()=>Method2(result2));` ... – Panagiotis Kanavos Jan 21 '22 at 16:42
  • @PanagiotisKanavos I'd generally agree - but throwing `async`/`await` at someone new to threading might be overkill... people tend to learn the TPL in a very bad way (think of all those "the waiting thread"-like questions...). The BackgroundWorker is much more simple to begin – Camilo Terevinto Jan 21 '22 at 16:44
  • 5
    @CamiloTerevinto async/await is a LOT simpler than trying to decipher BGW's quirks. Especially when the OP is already using async/await. BGW needs 10 lines to do the same thing that `Task.Run(()=>{...})` would do – Panagiotis Kanavos Jan 21 '22 at 16:45
  • @CamiloTerevinto would you mind elaborating on why it's bad to use `Process.GetCurrentProcess()`? Is it just wrong how OP is using it, or should the call be avoided altogether? – The2Step Jan 21 '22 at 16:46
  • 1
    @The2Step for starters, the current process isn't going to change. The code is trying to change the priority of the process though – Panagiotis Kanavos Jan 21 '22 at 16:47
  • 1
    @The2Step It's not going to change (^) and it's completely unnecessary - all of the logic involved with processes/threads/etc in the code presented here. It's too complicated and, obviously, doesn't work as the OP would want it to – Camilo Terevinto Jan 21 '22 at 16:49
  • @NewCoder there's too much unrelated code and too much *missing* code in the question. It looks like the relevant code is the call to `sensor.ReportSensors();` after which the results are shown ... somewhere. The class, the method and the `somewhere` are missing. All the rest of the code could be replaced with a loop that uses `var results=await Task.Run(()=>sensor.ReportSensors());` to read the data and then update the UI elements. This could even be in a `Timer`'s callback – Panagiotis Kanavos Jan 21 '22 at 16:53
  • @PanagiotisKanavos sensor.ReportSensors - is a method called from another class (SensorsService.cs) and using LibreHardwareMonitor library to obtain sensor readings. It's very doubtful that something is wrong with it since this is literally a code sample from the LHM developer. – New Coder Jan 21 '22 at 16:58
  • @NewCoder the point is that the question's code is wrong and the only thing that seems to be relevant is `sensor.ReportSensors()`. Isn't that what you want to run in the background? Where are the sensor readings stored? You should edit your question to show what you want to do, not how you think the problem can be solved. You don't need to change process priorities or thread affinity to run something in the background and update the UI – Panagiotis Kanavos Jan 21 '22 at 17:01
  • @PanagiotisKanavos "It's unclear what exactly you want to execute in the background" That is the problem. As I said in the question, I do not understand what exactly should be runing in the thread to make UI work smoothly when the CPU is under the load. – New Coder Jan 21 '22 at 17:03
  • 1
    @NewCoder that's not what I asked. What do you actually want to do? Forget how that can be done. Is it `sensor.ReportSensors()` or something else? Why do you want to do something more than just `await Task.Run(()=>sensor.ReportSensors());` in your *event handler* ? Your question won't be opened until 3 people are persuaded to vote to reopen – Panagiotis Kanavos Jan 21 '22 at 17:05
  • 1
    `async void TestButton_Clicked(...){ while(true){ await Task.Delay(3000); await Task.Run(()=>sensor.ReportSensors()); CPUTempTDie.Value = (int)sesnor.CpuTemp;}}` that's enough to take a measurement every 3s in the background and then update the UI. No matter how heavy `ReportSensors` is, the UI won't freeze while waiting – Panagiotis Kanavos Jan 21 '22 at 17:08
  • @PanagiotisKanavos If you read my question, I mentioned that affinity is set to cycle through each core and test every core of the processor. This is the purpose of the program, and it's not questionable. Affinity is being set because modern CPUs choose boost behavior based on load, temp, frequency of other cores, etc, and priority is necessary to allow the processor to boost higher during the stress-test, to test core stability at the edge of its performance limit. My question was closed while it seems like nobody even read it carefully – New Coder Jan 21 '22 at 17:10
  • @PanagiotisKanavos ReportSensors is not heavy, it consumes <0.1% of the core performance. The load comes from the TestingMethod as I mentioned in the code's comments. – New Coder Jan 21 '22 at 17:12
  • @NewCoder because we can't! The title asks one thing, then there's a wall of text and code that has little if anything to do with the title. If you want to avoid stalling, run the code in a background thread. It's that simple. – Panagiotis Kanavos Jan 21 '22 at 17:12
  • @PanagiotisKanavos The initial title was more descriptive. Someone changed it. If you have a suggestion for a better title, it would be truly appreciated. P.S.: Thank you, will do – New Coder Jan 21 '22 at 17:17

1 Answers1

2

You're setting the CPU affinity for the UI thread, then running the test routine on the same thread so it makes sense your UI is hanging during the test. Simplify things and ensure your UI/threading is working properly before you jump into actually performing your test routine.

private int TestingMethod()
{
    // set affinity for current thread here when ready

    // mock a busy thread by sleeping
    System.Threading.Thread.Sleep( 15 * 1000 );

    return 42;
}

// don't use `async void`
private async Task PerCoreTest()
{
    TestLabel.Text = "Running...";

    // we're in the UI thread, so we want to run
    // the test in another thread.  run a new
    // task to do so, await result so the continuation
    // will execute back in the UI thread
    var score = await Task.Run(() => TestingMethod());

    TestLabel.Text = score.ToString();
}

private async Task TestButton_Click(object sender, EventArgs e)
{
    await PerCoreTest();
}

Nice and simple. Add something else to the form that updates every second or so or a button you can click to verify the UI is updating properly as the test routine is running.

Once you've verified that the UI isn't locking up, then you may begin adding substance to your test routine. I suggest just getting a working test routine without processor affinity first.

private int TestingMethod()
{
    var score = 0;

    // set affinity for current thread here when ready

    do
    {
        // your cpu-frying logic
    }
    while( /* sentinel condition */ )

    return score;
}

Again, verify the UI is responsive during the test and you can also verify one of your cores is getting abused. Once all that is verified, you may then set the thread affinity INSIDE the TestingMethod() method's implementation (abstracting it to another method call is fine as well as long as it's called from within the TestingMethod's body and isn't run in a Task or another thread. You can pass the mask into TestingMethod as a parameter from the PerCoreTest method.

This should get you on the right track to doing what you want to do. I suggest you spend some quality time reading about multithreading in general and .NET's threading/asynchronous programming model if you plan on continuing with it in the future.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Moho
  • 15,457
  • 1
  • 30
  • 31