2

I have a method Limit() which counts a bandwidth passed thought some channel in certain time and limits by using Thread.Sleep() it (if bandwidth limit is reached). Method itself produces proper ( in my opinion results ) but Thread.Sleep doesn't ( due to multithreaded CPU usage ) because i have proper "millisecondsToWait" but speed check afterwards is far from limitation i've passed.

Is there a way to make limitation more precise ?

Limiter Class

    private readonly int m_maxSpeedInKbps;
    public Limiter(int maxSpeedInKbps)
    {
        m_maxSpeedInKbps = maxSpeedInKbps;
    }

    public int Limit(DateTime startOfCycleDateTime, long writtenInBytes)
    {
        if (m_maxSpeedInKbps > 0)
        {
            double totalMilliseconds = DateTime.Now.Subtract(startOfCycleDateTime).TotalMilliseconds;
            int currentSpeedInKbps = (int)((writtenInBytes / totalMilliseconds));
            if (currentSpeedInKbps - m_maxSpeedInKbps > 0)
            {
                double delta = (double)currentSpeedInKbps / m_maxSpeedInKbps;
                int millisecondsToWait = (int)((totalMilliseconds * delta) - totalMilliseconds);
                if (millisecondsToWait > 0)
                {
                    Thread.Sleep(millisecondsToWait);
                    return millisecondsToWait;
                }
            }
        }

        return 0;
    }

Test Class which always fails in large delta

[TestMethod]
public void ATest()
{
    List<File> files = new List<File>();
    for (int i = 0; i < 1; i++)
    {
        files.Add(new File(i + 1, 100));
    }

    const int maxSpeedInKbps = 1024; // 1MBps
    Limiter limiter = new Limiter(maxSpeedInKbps);

    DateTime startDateTime = DateTime.Now;
    Parallel.ForEach(files, new ParallelOptions {MaxDegreeOfParallelism = 5}, file =>
    {
        DateTime currentFileStartTime = DateTime.Now;
        Thread.Sleep(5);
        limiter.Limit(currentFileStartTime, file.Blocks * Block.Size);
    });

    long roundOfWriteInKB = (files.Sum(i => i.Blocks.Count) * Block.Size) / 1024;
    int currentSpeedInKbps = (int) (roundOfWriteInKB/DateTime.Now.Subtract(startDateTime).TotalMilliseconds*1000);

    Assert.AreEqual(maxSpeedInKbps, currentSpeedInKbps, string.Format("maxSpeedInKbps {0} currentSpeedInKbps {1}", maxSpeedInKbps, currentSpeedInKbps));
} 
eugeneK
  • 10,750
  • 19
  • 66
  • 101
  • What exactly do you mean by "far"? How far out are you talking? – Jon Skeet May 26 '13 at 12:37
  • Are you sure that the `Thread.Sleep` prevents further data from coming in on the channel? While it's true you won't get a whole lot of precision, I'm not convinced that's your problem. How often do you call `Limit`? – jerry May 26 '13 at 12:39
  • If you are sleeping for short durations then the actual sleep time will not be accurate - thread time slice is 15ms, so double or triple it for a minimum delta for reasonable accuracy. You can use a Stopwatch, however, to measure the actual time you've slept : http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx – J... May 26 '13 at 12:43
  • 1
    Actually in Windows 7 and later, `Thread.Sleep()` is pretty much millisecond accurate - but `DateTime.Now` certainly isn't. – Matthew Watson May 26 '13 at 12:43
  • OP might want to consider using [Multimedia timers](http://msdn.microsoft.com/en-us/library/windows/desktop/dd743609%28v=vs.85%29.aspx) – Matthew Watson May 26 '13 at 12:46
  • I'm not sure about Windows 7 or Windows 8, but I've seen the time slice on Vista be anywhere from 1 ms to 15 ms. – jerry May 26 '13 at 12:46
  • John, 2048 KBps results in 7xxx KBps. – eugeneK May 26 '13 at 12:47
  • jerry, each 50kbs of data ( matter of milliseconds ) – eugeneK May 26 '13 at 12:48
  • Matthew, what should i use along with Thread.Sleep() for Time count ? – eugeneK May 26 '13 at 12:49
  • @jerry I should have added "as long as the period is longer than around 10ms" because a timeslice is around that! – Matthew Watson May 26 '13 at 12:50
  • @eugeneK The most accurate timer that's not too hard to use is `Stopwatch` which uses the processor counters – Matthew Watson May 26 '13 at 12:51
  • possible duplicate of [Alternatives to Thread.Sleep() for simulating pauses](http://stackoverflow.com/questions/1457282/alternatives-to-thread-sleep-for-simulating-pauses) – Shadow The GPT Wizard May 26 '13 at 12:53
  • possible duplicate of [high frequency timing .NET](http://stackoverflow.com/questions/16630238/high-frequency-timing-net) – Hans Passant May 26 '13 at 12:57
  • @MatthewWatson I was under the impression that the default time slice varied. In any case, `timeBeginPeriod` used to cause it to change, not sure if that's still the case. – jerry May 26 '13 at 12:58
  • @eugeneK again, are you sure `sleep`ing this thread causes the data to stop? What happens if you `sleep` for, say, 10 seconds? What's the channel? How are you processing the incoming data? – jerry May 26 '13 at 13:02
  • @Matthew Watson, Stopwatch with conjunction with Thread.Sleep produces even less accurate time since it sleeps – eugeneK May 26 '13 at 13:04
  • @jerry, i'm sure no data is passing since i block next chunk of data to be sent by Limit(). Sleeps are 300ms-10s depends on limitation. – eugeneK May 26 '13 at 13:07
  • @eugeneK Well `Stopwatch` is definitely very accurate. – Matthew Watson May 26 '13 at 13:19
  • 1
    I see, the question didn't make it clear that you were sending. I blindly assumed you were receiving, seems I missed the fact that the parameter's called `writtenInBytes`. Anyway, for a delay of over 300 ms, I feel `Thread.Sleep` should be accurate enough given that you're calling `Limit` multiple times. What is the total data size compared to the size of a chunk (50 kb, I believe you said)? Are you willing to post a small compilable example that illustrates the problem? Also, I'm with @MatthewWatson in that you should use `Stopwatch` instead of `DateTime.Now`. – jerry May 26 '13 at 14:33
  • @jerry, example is done. With my current design i'm not sure i can use Stopwatch because it stops counting while thread sleep imho – eugeneK May 27 '13 at 05:42
  • `Stopwatch` may stop counting or reset if the **computer** sleeps or hibernates, but not just because you call `Thread.Sleep`. What makes you say that it does? I haven't had a chance to look over your code yet, I'll let you know if I see anything when I do. – jerry May 28 '13 at 15:41
  • @eugeneK This is not compilable. First thing I noticed is `new File(i + 1, 100)`. That's certainly not `System.IO.File`, so what is it? Also, this seems to be more complicated than necessary. For instance, why have a list and use `Parallel.ForEach` when there's only one item? – jerry May 29 '13 at 02:46
  • @jerry, this ain't IO just some data generated, then serialize as byte[]. 1 in for is just an example how 1 thread performs compared to 100. With one thread i get precise results with above one results getting by 2-4 larger. – eugeneK May 29 '13 at 08:21

2 Answers2

3

I used to use Thread.Sleep a lot until I discovered waithandles. Using waithandles you can suspend threads, which will come alive again when the waithandle is triggered from elsewhere, or when a time threshold is reached. Perhaps it's possible to re-engineer your limit methodology to use waithandles in some way, because in a lot of situations they are indeed much more precise than Thread.Sleep?

Stochastically
  • 7,616
  • 5
  • 30
  • 58
  • Do you mean to Limit using WaitHandle or to wrap the real bandwidth sending/reading tasks with it instead of Parallel.ForEach it is wrapped with now ? – eugeneK May 27 '13 at 11:33
  • When I responded with this answer, it was just a quick reaction to the title "Precise alternative to Thread.Sleep", because there no doubt that in some circumstances waithandles can be much more precise than `Thread.Sleep`. However, I'm not familiar with your bandwidth limit problem, so I'm not sure whether they're applicable in this situation. – Stochastically May 27 '13 at 17:26
3

You can do it fairly accurately using a busy wait, but I wouldn't recommend it. You should use one of the multimedia timers to wait instead.

However, this method will wait fairly accurately:

void accurateWait(int millisecs)
{
    var sw = Stopwatch.StartNew();

    if (millisecs >= 100)
        Thread.Sleep(millisecs - 50);

    while (sw.ElapsedMilliseconds < millisecs)
        ;
}

But it is a busy wait and will consume CPU cycles terribly. Also it could be affected by garbage collections or task rescheduling.

Here's the test program:

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Threading;

namespace Demo
{
    class Program
    {
        void run()
        {
            for (int i = 1; i < 10; ++i)
                test(i);

            for (int i = 10; i < 100; i += 5)
                test(i);

            for (int i = 100; i < 200; i += 10)
                test(i);

            for (int i = 200; i < 500; i += 20)
                test(i);
        }

        void test(int millisecs)
        {
            var sw = Stopwatch.StartNew();
            accurateWait(millisecs);
            Console.WriteLine("Requested wait = " + millisecs + ", actual wait = " + sw.ElapsedMilliseconds);
        }

        void accurateWait(int millisecs)
        {
            var sw = Stopwatch.StartNew();

            if (millisecs >= 100)
                Thread.Sleep(millisecs - 50);

            while (sw.ElapsedMilliseconds < millisecs)
                ;
        }

        static void Main()
        {
            new Program().run();
        }
    }
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • the problem is probably Parallel.ForEach() in which i run each IO thread. If i limit parallelism to 1 the results are precise. Must be thread.sleep, stops all threads as you said and not the one it runs on. – eugeneK May 27 '13 at 11:35
  • @eugeneK Thread.Sleep() really doesn't stop all threads. That would be horrendous! – Matthew Watson May 27 '13 at 11:38