0

In my console application I have a couple of classes (let's call them MyClass1, MyClass2, ...) having a method that should check the existence of certain records in the database (different classes wait for different records) and return only when the needed records exist. I currently have a simple implementation using an infinite loop and Thread.Sleep. This approach does work, but it tends to cause a high CPU load. What is the way to make these methods more CPU-friendly?

public override void WaitForRecord()
{
    MyDatabaseRecord record = null;

    while (record == null)
    {
        Thread.Sleep(500);
        using (var dc = new MyDataContext())
        {
            record = dc.Documents
                .Where( /*condition*/)
                .SingleOrDefault();
        }
    }   
    Logger.Info("Record with ID " + record.Id + " found at " + DateTime.Now)
}

The usage of these methods is pretty straightforward: the calling code creates a bunch of objects, launches each object's WaitForRecord method using Task.Factory.StartNew, periodically checks whether any tasks have finished execution, and prints the results in the console like this:

MyClass1 is still waiting for record...
MyClass2 has found the record...
...
Andre Borges
  • 1,360
  • 14
  • 37
  • Did you try with a timer? – Steve Aug 15 '16 at 13:52
  • where's the infinite loop? – default Aug 15 '16 at 13:53
  • Can you make the method `async` and use `await Task.Delay(500);`? – David Aug 15 '16 at 13:55
  • Actually, there seems to be ways already to track changes from a database. Check out [this thread](http://stackoverflow.com/questions/3364148/sql-server-database-change-listener-c-sharp) – default Aug 15 '16 at 13:56
  • 1
    @Default, I guess the name 'infinite' is not 100% correct. What I meant was 'a loop that has an indefinite number of iterations and can potentially be infinite' – Andre Borges Aug 15 '16 at 13:57
  • @David, is `Task.Delay` more CPU-efficient? I think I could try it – Andre Borges Aug 15 '16 at 13:58
  • @AndreBorges: I'm not sure actually, since `Thread.Sleep()` really shouldn't be taxing the CPU while it sleeps in the first place. Though at the very least replacing `Thread.Sleep()` with a "better" approach (a timer, awaiting an async delay, maybe something else) would at least clean up the logic a little bit. If something *else* is causing the problem, it might direct you to that. – David Aug 15 '16 at 14:02
  • @Steve, I don't know how to do it better with a timer. I still need to return from the method only when the timer finishes, which means checking some timer condition periodically before calling `return` – Andre Borges Aug 15 '16 at 14:03
  • The WaitForRecord seems a self contained method thus starting a timer with 500 ms interval and using the WaitForRecord as the EventHandler for Tick or Elapsed event and stopping the timer inside the WaitForRecord seems a fitting approach – Steve Aug 15 '16 at 14:08
  • 1
    @David, could you maybe point me to some articles explaining how these approaches are "better" exactly? – Andre Borges Aug 15 '16 at 14:08
  • A possible (albeit small) optimization could be to place your `using` statement outside of the loop so that the context isn't created and disposed every time. Another could be to use `dc.Documents.Any(/*condition*/);` instead of actually retrieving the record. That way you can even put that line in your `while`. – TheHvidsten Aug 15 '16 at 14:10
  • How is the record inserted in the first place. Can't that process signal your application somehow? Polling is the last resort for this kind of scenario. Does it really need to poll every 500ms? That will put a strain on your db as well, depending on the size of the table. – Peter Bons Aug 15 '16 at 14:30
  • 1
    See http://stackoverflow.com/questions/20082221/when-to-use-task-delay-when-to-use-thread-sleep for discussion of thread.sleep vs. task.delay. But really, neither are going to put load on the CPU. Presumably the load on the CPU is because your actual query takes time, and executing it every 500ms is fairly aggressive. Simple way to reduce the load is to increase the sleep time. – Mike Sackton Aug 15 '16 at 14:32
  • @AndreBorges There is no way doing 1 query ever 500 ms really consumes high CPU. Is your DB on the same machine? If so, please check if the DB process is the one consuming CPU (or disk I/O). Check the query plan and see if it requires some tuning. Also as Mike said, you can increase the delay between calls. – Gerardo Grignoli Aug 15 '16 at 17:13

1 Answers1

1

Assuming that you're connecting to a SQL (2005 or greater) database, you could look into SqlDependency. Here is an article on code project about SqlDependency and EF:

http://www.codeproject.com/Articles/496484/SqlDependency-with-Entity-Framework

Wyatt Earp
  • 1,783
  • 13
  • 23