0

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.

rory.ap
  • 34,009
  • 10
  • 83
  • 174
  • 2
    Call the Win32 `Sleep()` function and pass `0` as the sleep time. This will allow any other waiting threads to execute. – Carey Gregory Jan 19 '15 at 16:50
  • 2
    Why do you need to check the folder every 20 milliseconds? Checking it about once per second isn't enough? http://msdn.microsoft.com/it-it/library/system.threading.timer%28v=vs.110%29.aspx – fillobotto Jan 19 '15 at 16:51
  • 3
    I would suggest you use something like the [FileSystemWatcher](http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher(v=vs.110).aspx) instead. – the_lotus Jan 19 '15 at 16:52
  • @CareyGregory -- Thanks, but that doesn't help. Also, `Sleep()` is widely discouraged -- http://stackoverflow.com/questions/8815895/why-is-thread-sleep-so-harmful/8815944#8815944 – rory.ap Jan 19 '15 at 16:53
  • @the_lotus -- My question was more of a general one: when there is a lack of existing functionality and looping seems like the only way to do it. – rory.ap Jan 19 '15 at 16:53
  • 1
    Misuse of `Sleep` is widely discouraged but there's nothing wrong with it in and of itself. If adding a `Sleep(0)` call had no effect, then there was not enough demand on the processor to matter. It would be evident on a more heavily loaded system. As fillobotto pointed out, why check a directory every few milliseconds anyway? That's going to kill any machine. – Carey Gregory Jan 19 '15 at 16:55
  • Why do I keep seeing `Do While True` when all you need is `Do`? – Bjørn-Roger Kringsjå Jan 19 '15 at 16:56
  • @Bjørn-RogerKringsjå -- Great trick! File that one under "somehow missed despite years of coding in VB.NET" – rory.ap Jan 19 '15 at 16:59
  • @fillobotto -- That's actually a *very* good point. I put a `Thread.Sleep(1000)` in and now the CPU is <.1%. – rory.ap Jan 19 '15 at 17:01
  • 1
    @roryap Often time, these loops can be replaced by a messaging system. – the_lotus Jan 19 '15 at 17:01
  • @fillobotto -- If you can work that into a good answer, I'll accept it. Although I'd still let to get some input from someone on how the Windows event message loop manages to avoid the high CPU usage. I'm almost certain that loops every few milliseconds. – rory.ap Jan 19 '15 at 17:02
  • @roryap glad it helped, I'll write some code – fillobotto Jan 19 '15 at 17:03

3 Answers3

3

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     
Panu Oksala
  • 3,245
  • 1
  • 18
  • 28
  • 1
    My question is not about file system operations. It's about *any* operation that isn't awaitable. Using your answer may help in the one specific case shown in the example, but it doesn't help in other similar cases not involving watching the file system. – rory.ap Jan 19 '15 at 16:56
  • 1
    Oh, ok. The question was so specific about directory and files. – Panu Oksala Jan 19 '15 at 17:02
0

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.

fillobotto
  • 3,698
  • 5
  • 34
  • 58
0

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.

Taylor Kidd
  • 1,463
  • 1
  • 9
  • 11