8

Various answers suggest it is a bad idea to sleep inside a thread, for example: Avoid sleep. Why exactly? One reason often given is that it is difficult to gracefully exit the thread (by signalling it to terminate) if it is sleeping.

Let's say I wanted to periodically check for new files in a network folder, maybe once every 10s. This seems perfect for a thread with the priority set to low (or lowest) because I don't want the potentially time-consuming file I/O to impact my main thread.

What are the alternatives? Code is given in Delphi but would apply equally to any multi-threaded application:

procedure TNetFilesThrd.Execute();
begin
    try
        while (not Terminated) do
            begin
            // Check for new files
            // ...

            // Rest a little before spinning around again
            if (not Terminated) then
                Sleep(TenSeconds);
            end;
    finally
        // Terminated (or exception) so free all resources...
    end;
end;

A minor modification might be:

// Rest a little before spinning around again
nSleepCounter := 0;
while (not Terminated) and (nSleepCounter < 500) do
    begin
    Sleep(TwentyMilliseconds);
    Inc(nSleepCounter);
    end;

but this still involves a Sleep...

Community
  • 1
  • 1
AlainD
  • 5,413
  • 6
  • 45
  • 99
  • 1
    Sleeping in one thread can also be dangerous and lead to undefined behavior when using multi threading. – ITguy Nov 27 '15 at 15:37
  • 1
    The better alternative is to wait on a signal event for the same timeout; if the event is signaled you exit the thread immediately, if you have timeout you stay in the `while` loop. – kludg Nov 27 '15 at 15:38
  • 9
    @ITguy Sleeping has no inherent danger, and does not lead to undefined behaviour – David Heffernan Nov 27 '15 at 15:38
  • The solution would be completely different in C# and C++ and in C++ it might not even be on windows, this question is silly to be tagged in C# and C++. Editing question. Alain, making your question general is silly. Similarly silly question might be, how do I make dinner. (Instructions should work for any dinner.) – Warren P Nov 27 '15 at 19:35
  • @Warren General purpose question is fine. The answer is the same everywhere. – David Heffernan Nov 27 '15 at 20:00
  • Pretending that a while not terminated loop is general or is pseudo-code is silly. Nobody who googles and gets this answer is going to be helped by the C++ or C# tags. – Warren P Nov 27 '15 at 20:10
  • @Warren Maybe. My answer is half general, half specific. Oh well. – David Heffernan Nov 27 '15 at 20:20
  • Yeah, as a general windows answer, I would just suggest the person look at MSDN `WaitForSingleObject` api as an alternative to Win32 `Sleep` and `SleepEx`, and as a general sort of answer for all languages using Windows it would be more helpful. – Warren P Nov 27 '15 at 20:51
  • @WarrenP: A tad knee-jerk to call the question silly. "while (not X)" is just rewritten as "while (!X)". This issue applies to any of several languages and targets Windows, so perhaps the language itself (Delphi, C++, etc) is not relevant and "multithreading" and "Windows" would be the most accurate tags? Is the issue different on Linux? – AlainD Nov 27 '15 at 22:33
  • @ITguy'Sleeping in one thread can also be dangerous and lead to undefined behavior' WHAT? – Martin James Nov 28 '15 at 07:34
  • I use Sleep() quite often with no problems at all. Then again, very few of my threads are explicitly terminated and most only die at process termination. – Martin James Nov 28 '15 at 07:41
  • It's the "terminated" concept (a boolean class member of TThread) that doesn't actually always necessarily exist in C++, Alain. In fact, you wouldn't bother, you would simply wait on the event handle, and you would probably do a while-true, and break on event signalled. – Warren P Nov 28 '15 at 14:42

2 Answers2

10

The standard way to do this is to wait on a cancellation event. In pseudo code that looks like this:

while not Terminated do
begin
  // Check for new files
  // ...

  // Rest a little before spinning around again
  FTerminationEvent.WaitFor(TenSeconds);
end;

In order to terminate you would override TerminatedSet:

procedure TMyThread.TerminatedSet;
begin
  inherited;
  FTerminationEvent.SetEvent; // abandon the wait in the thread method
end;

The wait on the event either times out, or terminates because the event is signaled. This allows your thread to pause for a while and not burden the CPU, and yet also remain responsive to requests to terminate.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Do you know what the Delphi-7 unit (or D7 equivalent) for FTerminationEvent is? I'm busy Googling to try and find it but running down rabbit-holes at the moment!... – AlainD Nov 27 '15 at 15:50
  • 2
    @AlainD you need standart TEvent, FTerminationEvent is just variable name to understand what this event stands for. – Yuriy Afanasenkov Nov 27 '15 at 15:56
  • @YuriyAfanasenkov: Aah, ok thanks! Have now found this article (http://stackoverflow.com/questions/1734644/how-to-start-stop-a-monitoring-delphi-thread-on-demand) and am following it now. Will test it out and report back shortly. – AlainD Nov 27 '15 at 15:57
  • My thread, which is derived from TThread, has OnTerminate overridden but my override is not being signalled when I call TNetFilesThrd.Terminate(). Am investigating, but is there anything special I need to do to override OnTerminate? In my code, I'm hitting a breakpoint in the "finally" section where the thread's Terminate flag is now True, but not a breakpoint in my Terminate override. – AlainD Nov 27 '15 at 16:48
  • 1
    You cannot override `OnTerminate` since that is an event. I don't really want to comment on your code, which I cannot see. You asked the question in a general manner, not tied to any particular language. Hence details about your implementation are beyond the scope that you set. I think that you have the answer to the question. If you have problems implementing it, then that's a different matter. – David Heffernan Nov 27 '15 at 17:05
  • @DavidHeffernan: Understood, I'm trying to implement your comment "You would also override the Terminate method of the thread to signal the event." – AlainD Nov 27 '15 at 17:29
  • Not used this particular override before. When I try "procedure Terminate(); override;" the D7 compiler informs me that the method is static and not marked as virtual by the base class. When I remove the "override" it is not triggered. Am looking into the virtual method "DoTerminate" as I type this. – AlainD Nov 27 '15 at 17:50
  • Ah, yes, I think that's the one. Sorry. Doing this from memory. – David Heffernan Nov 27 '15 at 18:01
  • No problem, learning a lot, thanks! "DoTerminate" does get called, but only after the "Terminated" flag is set, so the TEvent::SetEvent line is called too late to be of benefit. BTW, SO is complaining about "extended discussion" so once I've got this implemented right, might delete these comments. – AlainD Nov 27 '15 at 18:09
  • 2
    The method `TThread.DoTerminate` is called **after** the `TThread.Execute` method has finished and it's main purpose is to call the `TThread.OnTerminated` event. The best option (newer Delphi versions) is to override the `TThread.TerminatedSet` method (but it is unknown to Delphi 7) – Sir Rufo Nov 27 '15 at 18:32
  • @SirRufo: OK thanks, that makes sense. Am stuck with D7 at the moment, so am investigating giving the TEvent global scope and setting the event just prior to calling TNetFilesThrd::Terminate. This is as per the D7 documentation after a more careful read. Looks like that works... – AlainD Nov 27 '15 at 18:36
  • I would reimplement the whole thread class in a custom unit and add the `TerminatedSet` method. These problems were the cause for Emba to modify the class in later versions. :o) – Sir Rufo Nov 27 '15 at 18:43
  • Gah, I've not been very good here. Thanks to everyone else for helping. The point is that you need to signal the event, and call `Terminate`. However the heck you do that in D7. Sorry for being so sloppy. I hope you get the concept though. – David Heffernan Nov 27 '15 at 18:43
  • @Sir Or subclass TThread and add a Terminate that hides the original. – David Heffernan Nov 27 '15 at 18:45
1

If this was my job, I think I would have solved it with a wrapper class with a TTimer in it, spawning a new thread every 10 seconds.

Spawning a new thread is somewhat costly, but if it's something that you do only every 10 seconds, the performance hit to the main thread is negligible, I think.

Steps:

  1. Create a wrapper class, TMyFileSearcher.
  2. Let it contain a TTimer.
  3. Each time the timer hits, spawn a new thread and search for the files.
  4. Add an OnTerminate handler to TMyFileSearcher, to process the returned files.

There'd be some other considerations too, such as keeping track of whether or not a thread has been spawned, so that you do not create an new thread while the old one is running.

But, other than this, I think it should be pretty straight forward to implement.

bjaastad_e
  • 691
  • 6
  • 10