0

I am trying to continually run code, to execute a function as quick as it can (unless there is a specified rate.) When I had use d java before, there was a scheduler, or timer, that I had used to run code continually within a certain delay, without multi-threading. I'm looking for something similar, but no delay.

I've already tried to multi-thread, and then use a while loop, with thread.sleep to add optional delays, and then invoke a method back to then main thread. The issue with this is that it freezes my process, and causes it to be "not responding" in task manager. Although it does not interrupt the main thread, which means it runs just as intended, rendering everything, it makes the task as "not responding", which disallows me from interacting with the maximize, close buttons, resizing, etc. This is my multithreading code.

public void Init()
{
            Thread thread = new Thread(Loop);
            thread.Start();
}

public void Loop()
{
            while (true)
            {
                if (InvokeRequired)
                {
                    this.BeginInvoke(new MethodInvoker(delegate { RenderFrame(); }));
                }

                if (maxFps > 0)
                {
                    Thread.Sleep(1000 / maxFps);
                }
            }
}

If anyone knows how I can get the same result without freezing the process, or do it without multi-threading, please give me some pointers!!

Edit: I've decided to just go with the timer, it is slightly slower, but that's ok, I guess. One last issue with it though, is that sometimes when closing or clicking off the winform my loop ends, and it stops calling the render function. I'm using this:

var timer = new System.Threading.Timer((e) =>
{
    this.BeginInvoke(new MethodInvoker(delegate { RenderFrame(); }));
}, null, 0, 1);
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
okko
  • 11
  • 5
  • Use Task parallel library. There is plenty of tutorials on the internet. – geldek Oct 30 '22 at 17:59
  • Does it still happen with an empty while loop? I'd expect CPU to go 100%, but the process freezing is unexpected. – Sedat Kapanoglu Oct 30 '22 at 18:02
  • 1
    Well, mission accomplished, that code runs as quick as it can. Crowding out the other things that a UI thread needs to do, like responding to user input. It will eventually crash with OutOfMemoryException, but that takes a while since you have a lot of it. Best to throw it away, there isn't enough relevant code to suggest a better approach. A timer is better than nothing. – Hans Passant Oct 30 '22 at 18:10
  • @HansPassant you've really misunderstood my qustion. I wasn't asking how to repeat code as quick as possible, I was asking how I can repeat code, the same way, or another way, but without the process freezing, and if I use a different method, how I can make sure it doesn't use a delay, for example, a timer. – okko Oct 30 '22 at 19:14
  • 1
    @SedatKapanoglu doesn't seem to happen with an empty while loop. Must be that code is running more frequently on the UI thread than it should be? But I don't know how I could repeat a task for the main thread in a safer way. – okko Oct 30 '22 at 19:15
  • Ok, I have used a timer with a delay of 1ms, which may mean that it has, barely, a delay. However, it runs a lot slower, and gets worse preformance that my previous method, it does fix the freezing, though. The code is here: https://hastebin.com/qivobudame.js. (For some reason, it also causes my program to just stop running though? Without the not responding type of freezing) – okko Oct 30 '22 at 19:22
  • 1
    What does the background function actually do? - it doesn’t make sense to update the UI more than once every 1/10 second, if that. – 500 - Internal Server Error Oct 30 '22 at 19:25
  • @500-InternalServerError I'm assuming you mean the RenderFrame function? It clears the screen and renders lines, to form a 3d cube shape. – okko Oct 30 '22 at 19:26
  • Related questions: [BeginInvoke is blocking the UI, whereas Invoke is not. Why?](https://stackoverflow.com/questions/10497690/begininvoke-is-blocking-the-ui-whereas-invoke-is-not-why) and also [Unresponsive UI when using BeginInvoke](https://stackoverflow.com/questions/2510829/unresponsive-ui-when-using-begininvoke). – Theodor Zoulias Oct 30 '22 at 19:50

1 Answers1

2

You are starting a thread that is doing nothing else but bombard the UI message loop with millions of messages. That's not a good use of a thread. You need a background thread when you have work to do that is not UI-related, and in your case it seems that all the work is associated with manipulating UI components. So all the work should happen on the UI thread. In order to keep the UI responsive while the infinite loop is running, you can convert the Loop to an async method (async Task or async void), and replace the Thread.Sleep() with await Task.Delay():

public void Init()
{
    Loop();
}

public async void Loop()
{
    while (true)
    {
        RenderFrame();
        if (maxFps > 0)
            await Task.Delay(1000 / maxFps);
        else
            await Task.Yield();
    }
}

Instead of while (true) you could check the condition that the main form is still open, otherwise the loop will continue after closing the form, resulting most likely in an ObjectDisposedException.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • This is good, and works about the same as what I had originally, however, the UI frame still does not respond to input, and is marked as "not responding" in task manager, even though the rendering still occurs. – okko Oct 30 '22 at 19:49
  • Nevermind, it seems to work, although slowly, if I set maxFps greater than 0. – okko Oct 30 '22 at 19:51
  • @okko yes, the [`await Task.Yield()`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.yield) is something to avoid in general. It's not giving much time to anything else to happen. You could replace it with `await Task.Delay(1)`. – Theodor Zoulias Oct 30 '22 at 19:54
  • That makes sense, however, for some reason using delay 1 ends up resulting in maybe, 30 calls per second, when yielding, gives about 120, but freezes the task. – okko Oct 30 '22 at 19:56
  • @okko the `Task.Delay` is not precise to the millisecond. With the default clock frequency, the minimum delay is about 15 msec. Check out this question for more details: [Why are .NET timers limited to 15 ms resolution?](https://stackoverflow.com/questions/3744032/why-are-net-timers-limited-to-15-ms-resolution) – Theodor Zoulias Oct 30 '22 at 19:59
  • But, 15ms implies it can run solely off the CPU with 65, , which it was doing when using a timer, but this, it calls much under 65, about half, which doesn't make sense, considering it was capable of doing 120 previously. – okko Oct 30 '22 at 20:05
  • @okko the 15 msec is the minimum delay. The actual delay might be up to double than that. I am not sure that using a timer to render frames is a good idea to be honest, but I have no experience with game-like applications to offer better advice. – Theodor Zoulias Oct 30 '22 at 20:11