rory.ap
rory.ap

Reputation: 35318

How to loop continuously without using a lot of CPU

I have an application that runs a task which checks for a file in a directory and completes when a file has been added to the directory. Here's a simplified example:

Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim addedFile = Await Task.Factory.StartNew(New Func(Of FileInfo)(
        Function()
            Dim di = New DirectoryInfo("C:\_Temporary\Test")

            Do While True
                Dim files = di.GetFiles()

                If (files.Count > 0) Then
                    Return files(0)
                End If
            Loop
        End Function))

    MsgBox(addedFile.FullName)
End Sub

I've left out superfluous details like cancellation tokens, etc.

The issue is that the CPU is holding steady around 12% when the code is running. Even if I comment out the body inside the while loop, it remains the same.

How can I create a looping mechanism, which is required for non-awaitable operations like waiting for a file to arrive in a directory, without using that much CPU?

Note: The question is not about the concrete case involving the file system; it's looping non-awaitable operations in general and the effect on the CPU.

The Windows event message loop, by contrast, takes up less than 1% -- e.g. if I look at the CPU usage of my app before I click "Button1" which runs the above code.

Upvotes: 0

Views: 1061

Answers (3)

Taylor Kidd
Taylor Kidd

Reputation: 1511

You are polling. Polling is never a good idea for a number of reasons. Polling is very common in software, even in some well known commercial software that will remain nameless. It causes a host of problems from tying up the processor unnecessarily, as you already know, to preventing the processor from sleeping and causing your battery to drain faster than it would otherwise.

SOLUTION #1: The most desirable solution is to find a built-in watcher as someone else pointed out. Under the covers, the watcher is probably using either a specific interrupt mechanism or using a kernel timing routine to emulate an interrupt.

SOLUTION #2: If no such watcher exists, you can use a for/while loop with a sleep() to check every so many seconds: It checks, goes to sleep for an interval, and then checks again. The sleep() function will not use any processing time (not completely true but practically so).

Even with Solution #2, you can fall into some traps that will cause you to use more processing than you need to. Do a realistic analysis of how often you need to check your condition. A very common mistake is to set a very short polling period under the mistaken assumption that your application will react faster.

If your typical event occurs once every minute or two, and you need to react to the event within 5 seconds, then having a polling time of 10 msec doesn't buy any gain and hurts everyone's performance. In this situation, you can get away with polling every 2 seconds, almost three orders of magnitude less frequently than 10 ms.

Another thing that many aren't aware of is that the underlying resolution of timers, etc., in typical operating systems (e.g. Linux and Windows) is around 10 msec even though the data structures let you specify potentially into the microseconds.

Upvotes: 0

fillobotto
fillobotto

Reputation: 3785

So, it's better to check for your files once per second (however not using a While True loop, always avoid them).

Dim timerDelegate As TimerCallback = AddressOf RepeatingFunction
Dim autoEvent As New AutoResetEvent(True)
Dim dt As New System.Threading.Timer(timerDelegate, autoEvent, 0, 1000)

MSDN: http://msdn.microsoft.com/it-it/library/system.threading.timer%28v=vs.110%29.aspx

Note: RepeatingFunction has to be repleaced. Also the last parameter of Threading.Timer constructor reprents the interval between ticks.

Upvotes: 0

Panu Oksala
Panu Oksala

Reputation: 3438

Use Filewatcher class to receive events when directory changes: http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher%28v=vs.110%29.aspx

' Create a new FileSystemWatcher and set its properties. 
Dim watcher As New FileSystemWatcher()
watcher.Path = "C:\_Temporary\Test"
' Watch for changes in LastAccess and LastWrite times, and 
' the renaming of files or directories. 
watcher.NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName)
' Only watch text files.
watcher.Filter = "*.txt" 

' Add event handlers. 
AddHandler watcher.Created, AddressOf OnChanged

' Define the event handlers. 
Private Shared Sub OnChanged(source As Object, e As FileSystemEventArgs)
   ' Specify what is done when a file is changed, created, or deleted.
   MsgBox(e.FullPath)
End Sub     

Upvotes: 3

Related Questions