【问题标题】:Precise alternative to Thread.SleepThread.Sleep 的精确替代方案
【发布时间】:2013-05-26 12:33:41
【问题描述】:

我有一个方法 Limit(),它计算在特定时间内通过某个通道的带宽,并通过使用 Thread.Sleep() 来限制它(如果达到带宽限制)。 方法本身产生正确的(在我看来结果)但 Thread.Sleep 没有(由于多线程 CPU 使用),因为我有正确的“毫秒ToWait”,但之后的速度检查远非我通过的限制。

有没有办法让限制更精确?

限制器类

    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;
    }

在大增量中总是失败的测试类

[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));
} 

【问题讨论】:

  • “远”到底是什么意思?你说的有多远?
  • 您确定Thread.Sleep 会阻止更多数据进入通道吗?虽然确实你不会得到很多精确度,但我不相信这是你的问题。您多久拨打一次Limit
  • 如果你的睡眠时间很短,那么实际的睡眠时间将不准确 - 线程时间片是 15 毫秒,因此将其加倍或三倍以获得合理准确度的最小增量。但是,您可以使用秒表来测量您的实际睡眠时间:msdn.microsoft.com/en-us/library/…
  • 实际上在 Windows 7 及更高版本中,Thread.Sleep() 几乎可以精确到毫秒 - 但DateTime.Now 肯定不是。
  • 我明白了,这个问题并没有说明您正在发送。我盲目地假设您正在接收,似乎我错过了参数称为writtenInBytes 的事实。无论如何,对于超过 300 毫秒的延迟,我觉得 Thread.Sleep 应该足够准确,因为您多次调用 Limit。与块的大小(50 kb,我相信你说)相比,总数据大小是多少?您是否愿意发布一个说明问题的小型可编译示例?另外,我支持@MatthewWatson,因为您应该使用Stopwatch 而不是DateTime.Now

标签: c# .net


【解决方案1】:

我曾经经常使用Thread.Sleep,直到我发现waithandles。使用等待句柄,您可以暂停线程,当等待句柄从其他地方触发或达到时间阈值时,线程将再次活跃。也许可以重新设计您的限制方法以以某种方式使用等待句柄,因为在很多情况下它们确实比Thread.Sleep 精确得多?

【讨论】:

  • 你的意思是限制使用 WaitHandle 还是用它而不是 Parallel.ForEach 包装真正的带宽发送/读取任务?
  • 当我回答这个答案时,这只是对标题“精确替代 Thread.Sleep”的快速反应,因为毫无疑问,在某些情况下等待句柄可能比 @987654324 更精确@。但是,我不熟悉您的带宽限制问题,所以我不确定它们是否适用于这种情况。
【解决方案2】:

您可以使用繁忙的等待来相当准确地完成此操作,但我不建议这样做。您应该改用one of the multimedia timers 等待。

但是,这种方法会相当准确地等待:

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

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

    while (sw.ElapsedMilliseconds < millisecs)
        ;
}

但这是一个忙碌的等待,并且会非常消耗 CPU 周期。它还可能受到垃圾收集或任务重新调度的影响。

这是测试程序:

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();
        }
    }
}

【讨论】:

  • 问题可能是我在其中运行每个 IO 线程的 Parallel.ForEach()。如果我将并行度限制为 1,则结果是精确的。必须是thread.sleep,按照你说的停止所有线程,而不是它运行的那个。
  • @eugeneK Thread.Sleep() 确实不会停止所有线程。那太可怕了!
猜你喜欢
  • 1970-01-01
  • 2012-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多