Имитация задержки сообщения точно - PullRequest
0 голосов
/ 31 августа 2018

Я написал простой «симулятор задержки», который работает, но иногда сообщения задерживаются дольше указанного времени. Мне нужна помощь, чтобы сообщения задерживались на правильное время.

Основная проблема, я полагаю, заключается в том, что я использую Thread.Sleep(x), что зависит от различных факторов, но в основном от частоты тактовых прерываний , из-за которой Thread.Sleep() имеет разрешение примерно 15мс. Кроме того, интенсивные задачи потребуют больше процессорного времени и иногда приводят к задержке, превышающей запрошенную. Если вы не знакомы с проблемами разрешения Thread.Sleep, вы можете прочитать эти сообщения SO: здесь , здесь и здесь .

Это мой LatencySimulator:

public class LatencySimulatorResult: EventArgs
{
    public int messageNumber { get; set; }
    public byte[] message { get; set; }
}

public class LatencySimulator
{
    private int messageNumber;
    private int latency = 0;
    private int processedMessageCount = 0;

    public event EventHandler messageReady;

    public void Delay(byte[] message, int delay)
    {
        latency = delay;

        var result = new LatencySimulatorResult();
        result.message = message;
        result.messageNumber = messageNumber;

        if (latency == 0)
        {
            if (messageReady != null)
                messageReady(this, result);
        }
        else
        {
            ThreadPool.QueueUserWorkItem(ThreadPoolCallback, result);
        }
        Interlocked.Increment(ref messageNumber);
    }

    private void ThreadPoolCallback(object threadContext)
    {
        Thread.Sleep(latency);
        var next = (LatencySimulatorResult)threadContext;

        var ready = next.messageNumber == processedMessageCount + 1;
        while (ready == false)
        {
            ready = next.messageNumber == processedMessageCount + 1;
        }

        if (messageReady != null)
            messageReady(this, next);

        Interlocked.Increment(ref processedMessageCount);
    }
}

Чтобы использовать его, вы создаете новый экземпляр и привязываете его к обработчику событий:

var latencySimulator = new LatencySimulator();
latencySimulator.messageReady += MessageReady;

Затем вы звоните latencySimulator.Delay(someBytes, someDelay); Когда сообщение закончено с задержкой, событие запускается, и вы можете обработать отложенное сообщение.

Важно, чтобы порядок добавления сообщений сохранялся. Я не могу заставить их выходить на другой конец симулятора задержки в каком-то случайном порядке.

Вот тестовая программа для использования симулятора латентности и просмотра времени задержки сообщений:

private static LatencySimulator latencySimulator;
private static ConcurrentDictionary<int, PendingMessage> pendingMessages;
private static List<long> measurements;

static void Main(string[] args)
{
    var results = TestLatencySimulator();
    var anomalies = results.Result.Where(x=>x > 32).ToList();
    foreach (var result in anomalies)
    {
        Console.WriteLine(result);
    }

    Console.ReadLine();
}

static async Task<List<long>> TestLatencySimulator()
{
    latencySimulator = new LatencySimulator();
    latencySimulator.messageReady += MessageReady;
    var numberOfMeasurementsMax = 1000;
    pendingMessages = new ConcurrentDictionary<int, PendingMessage>();
    measurements = new List<long>();

    var sendTask = Task.Factory.StartNew(() =>
    {
        for (var i = 0; i < numberOfMeasurementsMax; i++)
        {
            var message = new Message { Id = i };
            pendingMessages.TryAdd(i, new PendingMessage() { Id = i });
            latencySimulator.Delay(Serialize(message), 30);
            Thread.Sleep(50);
        }
    });

    //Spin some tasks up to simulate high CPU usage
    Task.Factory.StartNew(() => { FindPrimeNumber(100000); });
    Task.Factory.StartNew(() => { FindPrimeNumber(100000); });
    Task.Factory.StartNew(() => { FindPrimeNumber(100000); });

    sendTask.Wait();

    return measurements;
}

static long FindPrimeNumber(int n)
{
    int count = 0;
    long a = 2;
    while (count < n)
    {
        long b = 2;
        int prime = 1;// to check if found a prime
        while (b * b <= a)
        {
            if (a % b == 0)
            {
                prime = 0;
                break;
            }
            b++;
        }
        if (prime > 0)
        {
            count++;
        }
        a++;
    }
    return (--a);
}

private static void MessageReady(object sender, EventArgs e)
{
    LatencySimulatorResult result = (LatencySimulatorResult)e;

    var message = (Message)Deserialize(result.message);
    if (pendingMessages.ContainsKey(message.Id) != true) return;

    pendingMessages[message.Id].stopwatch.Stop();
    measurements.Add(pendingMessages[message.Id].stopwatch.ElapsedMilliseconds);
}

static object Deserialize(byte[] arrBytes)
{
    using (var memStream = new MemoryStream())
    {
        var binForm = new BinaryFormatter();
        memStream.Write(arrBytes, 0, arrBytes.Length);
        memStream.Seek(0, SeekOrigin.Begin);
        var obj = binForm.Deserialize(memStream);
        return obj;
    }
}

static byte[] Serialize<T>(T obj)
{
    BinaryFormatter bf = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        bf.Serialize(ms, obj);
        return ms.ToArray();
    }
}

Если вы запустите этот код, вы увидите, что около 5% сообщений задерживаются более чем на ожидаемые 30 мс. На самом деле, некоторые достигают 60 мс. Без каких-либо фоновых задач или высокой загрузки ЦП симулятор ведет себя как положено.

Мне нужно, чтобы все они составляли 30 мс (или как можно ближе) - я не хочу каких-либо произвольных задержек в 50-60 мс.

Кто-нибудь может подсказать, как я могу реорганизовать этот код, чтобы я мог достичь желаемого результата, но без использования Thread.Sleep() и с наименьшей нагрузкой на процессор?

...