35

I have a method that uses a background worker to poll a DLL for a status looking something like this:

var timeout = DateTime.Now.AddSeconds(3);
while (System.Status != Status.Complete  // our status is not complete
       && DateTime.Now < timeout         // have not timed out
       && !_Worker.CancellationPending)  // backgroundworker has not been canceled
{
    //Thread.Yield();
    //Thread.SpinWait(1);
    //Thread.Sleep(1);
}

When looking at my CPU %, yield() and spinwait() cause my app to shoot up to 50% on my PC. With Sleep(1) my CPU % stays down at 6%. I have been told that that I should choose Thread.Yield(), however the spikes in CPU % bother me. What is best practice for something like this?

ruffin
  • 16,507
  • 9
  • 88
  • 138
poco
  • 2,935
  • 6
  • 37
  • 54
  • 2
    Have you considering using a `Threading.Timer` for this polling process? If you are using .NET 4.0 you can mix that in with the TPL to have task-based cooperative cancellation. – Bryan Crosby Jul 14 '12 at 04:07
  • I'm guessing you have 2 cores on your PC. One of them is 100% busy... – Eric J. Jul 14 '12 at 04:10
  • 4
    Why would you use a background worker only to wait for it? That defeats all common sense. And why doesn't the DLL provide a sensible way to wait for an operation to complete if you need to do that? – David Schwartz Jul 14 '12 at 04:45
  • 1
    +1 @DavidSchwartz for suggesting efficient signaling instead of CPU-wasting and latency-ridden polling, (and there are many, AutoResetEvent, Semaphore etc etc). – Martin James Jul 14 '12 at 08:51
  • 2
    @poco tl;dr you've been told *wrong*. –  Jul 09 '15 at 13:59

2 Answers2

36

Thread.Yield will interrupt the current thread to allow other threads to do work. However, if they do not have any work to do, your thread will soon be rescheduled and will continue to poll, thus 100% utilization of 1 core.

Causes the calling thread to yield execution to another thread that is ready to run on the current processor. The operating system selects the thread to yield to.

Thread.Sleep will schedule your thread to run again after the sleep time expires, thus much lower CPU utilization.

Blocks the current thread for the specified number of milliseconds.

Given the choice between the two, Thread.Sleep is better suited for your task. However, I agree with the comment from @Bryan that a Threading.Timer makes for a more elegant solution.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • 2
    Neither of the solutions involving polling are remotely 'elegant'. Such a solution should only be used in desperation when there is absolutely no other way of communicating with the DLL, eg. it's 3rd-party, opaque and badly designed, (in which case, the designer should be repeatedly hit about the head with an OS manual:). – Martin James Jul 14 '12 at 08:54
  • 1
    Sure. I assumed this constraint when the problem stated that an external DLL must be polled. – Eric J. Jul 14 '12 at 15:25
  • 2
    Thread.Yield actually tells the OS to take priority away from your process (rather than Thread.Sleep, which makes the OS figure it out for itself). When I use Thread.Yield I combine it with Thread.Sleep -- so the code is basically `Thread.Yield(); Thread.Sleep(1);` -- Note that, Thread.Sleep will almost always sleep for a minimum of 10ms (I have not seen a way to get better than 10ms timing resolution without using unmanaged code or third party libraries). – BrainSlugs83 Dec 12 '13 at 20:22
  • 1
    Do you have sources about this 10ms minimum delay? We had issues when running Thread.Sleep(interval) when interval is set to zero. – Rafael Diego Nicoletti Jan 18 '14 at 01:53
  • @RafaelDiegoNicoletti http://stackoverflow.com/questions/19066900/thread-sleep1-takes-longer-than-1ms – JaredBroad Sep 25 '14 at 04:57
  • will thread.Yield release the lock if it is asked to yield in a lock ? – Muds Feb 19 '19 at 15:28
  • @Muds Try it... And if you have trouble ask a **new** question here on Stackoverflow... But a question in a comment on another question that is already **5 years old** is very bad. – Mischa Feb 20 '19 at 13:33
  • definitely I should try it myself, But .. Not sure of which protocol am I in breach of if I post a related question to a question that was asked 6 years and 7 months ago. Saying that I believe, one must comment that adds any value to the forum and just not bloats the thread. I asked it here so that whenever someone comes here looking for info like myself gets all related info in one thread and has one less hurdle to face. Also please read policy about what all words are candidate of making **bold** Thanks for the suggestion, though a reply is still awaited. – Muds Feb 20 '19 at 14:37
  • @MartinJames ancient thread here, but I just wanted to add that the OS manual should be a big heavy one for a DEC PDP-7 with pages and pages of octal conversion tables. Or even the PDP-10 Interface Manual, which is conveniently available in digital (no pun intended) form here: http://www.bitsavers.org/www.computer.museum.uq.edu.au/pdf/DEC-10-HIFB-D%20PDP-10%20Interface%20Manual.pdf – Craig Tullis Mar 10 '22 at 06:23
4

Answering this question from the post message:

What is best practice for something like this?

You seem to have a case for the SpinWait struct, which exists since .NET 4.0 and is different than the much older SpinWait method.

Your code can be upgraded to something like this:

var timeout = DateTime.Now.AddSeconds(3);
var spinWait = new SpinWait();
while (System.Status != Status.Complete
       && DateTime.Now < timeout
       && !_Worker.CancellationPending)
{
    spinWait.SpinOnce();
}

The SpinOnce method will decide whether to call Thread.Sleep, Thread.Yield or do a "busy wait" where there is no yield.

Specifically about what the post title (i.e. Thread.Sleep vs Thread.Yield), here are some up-to-date answers:

  • Thread.Yield(): If there is another thread ready to execute, then this gives away the current CPU core to that thread. Otherwise does nothing, i.e. returns immediately, which may cause high CPU (and high battery consumption) if the system has at least one idle CPU core. In most cases, resumes execution in the same CPU core, but this is not guaranteed for a number of reasons (the thread might be suspended by GC, etc). On server space, it's a great way to implement a simple busy wait, but it may be slower than the above explained SpinWait in some cases. On Windows systems, it is (as of this writing) implemented as call to Win32 API SwitchToThread.
  • Thread.Sleep(0): Puts the current thread in the end of OS ready-to-execute queue for that priority, if the queue is not empty. Otherwise does nothing, i.e. returns immediately. The behavior is almost identical to Thread.Yield(), except that it allows switching the CPU core, which tends to give more time to threads with same or lower priority before resuming. In addition to this additional time, the odds of resuming on a different CPU core are much greater, causing things like L1 or L2 cache to be lost, and further decrease performance. It should be used only when odds of starvation are higher than using Thread.Yield(). On Windows systems, it is (as of this writing) implemented as call to Win32 API SleepEx.
  • Thread.Sleep(1): If the remainder of the timeslice has at least 1ms, gives the CPU away for the remainder of the timeslice. Otherwise, gives the CPU away for the remainder of the timeslice AND the entire next timeslice. Because in many systems the timeslice is about 15ms, it tends to sleep for about 7ms or 8ms in average. Therefore, different than Thread.Yield() and Thread.Sleep(0), the Thread.Sleep(1) is guaranteed to give the CPU away for some time, which may cool things down. However, the performance cost is huge, and for that reason it should only be used in conjunction with other "spinning" or "yielding" solutions.

Additional comment about this:

however the spikes in CPU % bother me

From your post, that concern might be because the code has a 3-second timeout. The SpinWait should take care of that, but you can't use the SpinWait struct for some reason, consider using a smaller timeout, in the order of milliseconds.

fernacolo
  • 7,012
  • 5
  • 40
  • 61