0

I'm doing this test on my machine. Obviously cpu % will vary, but I'm more interested in understanding what's going on. I simply create a new, blank, Console .Net Framework application (--not-- .net core). Here is the source of "Program.cs":

static void Main(string[] args)
    {
        myClass myClass = new myClass();
        myClass.myInifiniteMethodAsync();
        Console.WriteLine("Launched...");
        Console.ReadLine();
    }

Then this is the source of myClass:

class myClass
{
    public async Task myInifiniteMethodAsync()
    {
        await Task.Run(() => myInfiniteMethod());
    }
    public void myInfiniteMethod()
    {
        //do some things but keep this thread holded...

        //bool keepRunning = true;  
        //while (keepRunning)       {   } <--- this one takes 30% cpu...
        Console.ReadLine(); // <--- this one takes 5% cpu...
    }
}

I need the IfiniteMethod to stay always there, "holding" the thread forever. If I use the "while(true)" method, the CPU raises up to 30%. If I use the Console.ReadLine() method, the CPU stays around 5%.

I would like to understand why, and if there's a better method to hold a thread.

BitQuestions
  • 651
  • 7
  • 14

3 Answers3

6

In order to answer your question, we need to understand how the operating system performs each of these lines.

For while(true) {}, the compiled code consists of a comparison of the variable keepRunning, and then makes a conditional jump. The important part of it is that the operating system continuously performs instructions.

However, for Console.ReadLine(), it waits for user input, for which the operating system only waits for a hardware interrupt (key press), and doesn't need to continuously perform instructions.

Therefore, the loop version requires the operating system to give your program as much time as it can, because it "wants" to run many instructions, whereas for the input version the operating system only waits for a hardware interrupt, and the program doesn't need to perform any other instructions.

Meccano
  • 537
  • 4
  • 8
  • Ok, thank you for the explanation. I need this thing for a large enterprise project... it looks so "unprofessional" to me, to release something with a "Console.Readline" to hold that thread... is there a better way to obtain the same result ? – BitQuestions Jun 13 '20 at 09:13
  • 2
    You *could* mitigate the cpu usage in the `while(true)` case by adding in a `Thread.Sleep`- - even one with a very small duration -- to prevent the constant execution – pinkfloydx33 Jun 13 '20 at 10:00
  • @pinkfloydx33 you are right, the difference adding a simple Thred.Sleep(1) inside the while(true) loop makes a big difference in terms of CPU. Anyway, at the end I preferred the manualresetevent solution I found later, it looks sort of more "elegant" to me :-) and it takes the same cpu. – BitQuestions Jun 13 '20 at 13:45
1

What"s the real purpose of the infinite method? Waiting implementation depends on it. But in most cases it's a Producer-Consumer programming pattern. I suggest BlockingCollection or brand-new Channel in that case.

Here's an example for BlockingCollection

static void Main(string[] args)
{
    BlockingCollection<string> messages = new BlockingCollection<string>();
    Consumer consumer = new Consumer(messages);
    consumer.Start();
    Console.WriteLine("Consumer launched");
    Console.WriteLine("Starting main loop (hit Enter to exit)");
    while (true)
    {
        string m = Console.ReadLine();
        if (m.Length > 0)
            messages.Add(m);
        else
            break;
    }
    consumer.Stop();
    Console.WriteLine("Main loop finished");
    Console.ReadKey();
}

public class Consumer
{
    private BlockingCollection<string> _messages;

    public Consumer(BlockingCollection<string> messages)
    {
        _messages = messages;
    }

    private void RunLoop()
    {
        Console.WriteLine("Starting consumer loop");
        foreach (string message in _messages.GetConsumingEnumerable()) // <--- this one takes 0% cpu
        {
            Console.WriteLine("Got message: {0}", message);
        }
        Console.WriteLine("Consumer loop finished");
    }

    public void Start()
    {
        Task.Run(() => RunLoop());
    }

    public void Stop()
    {
        _messages.CompleteAdding();
    }
}

And the output

Consumer launched
Starting main loop (hit Enter to exit)
Starting consumer loop
test message 1
Got message: test message 1
blablabla
Got message: blablabla

Main loop finished
Consumer loop finished

You need no async/await here but only one Task.

aepot
  • 4,558
  • 2
  • 12
  • 24
  • Thank you, your answer looks quite interesting and with a lot of new things for me to study in deep. In my case, I need to "hold" a Signal-R server hub connection in an enterprise application that does other things at the same time, so the idea was to run it with async, and let it go on a separate thread. – BitQuestions Jun 13 '20 at 13:50
0

Ok, as for the "better method" to hold the thread while having a low cpu consume, I found an answer: the ManualResetEvent !

Instead of:

while (true); //or equivalent while (somevar)

Now I simply do:

ManualResetEvent resetEvent = new ManualResetEvent(false);
resetEvent.WaitOne();       
BitQuestions
  • 651
  • 7
  • 14