Темп выполнения операций

156
11 мая 2018, 09:16

Имеется очередь каких-то атомарных операций, в моем случае это порции данных, которые загружаются в БД, также это может быть, например, очередь файлов при копировании. Я хочу вычислять и выводить темп выполнения этих операций, ну, например, чтобы в любой момент я видел, что за последнюю минуту выполнилось 20 операций или, например, 120. Супер точность при этом не нужна - отклонение в 2-5 единиц считаю допустимым. Параллельно в эту же очередь извне могут добавляться еще порции данных, поэтому просто делать отпечаток количества элементов в очереди не получится. Как это реализовать?

Пока набирал вопрос, возникло примерно вот такое решение:

using System;
using System.ComponentModel;
using System.Linq;
using System.Timers;
class Program : INotifyPropertyChanged
{
    static void Main(string[] args) => new Program().Run();
    int count = 0;
    int[] tablet = new int[60];
    public int Tempo => tablet.Sum();
    public event PropertyChangedEventHandler PropertyChanged;
    void Snick()
    {
        ++count;
    }
    void Cut()
    {
        int s = DateTime.Now.Second;
        tablet[s] = count;
        count = 0;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Tempo)));
    }
    void Run()
    {
        Timer timer = new Timer(1000);
        timer.Elapsed += (s, e) => Cut();
        timer.Start();
        // ...
        while (true)
        {
            // ...
            Snick();
        }
        timer.Stop();
    }
}

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

Может у кого-то есть уже готовое решение, которое лучше?

Answer 1

А почему так сложно?

Вот такой простой код должен работать:

TimeSpan timeout = TimeSpan.FromMinutes(1);
long OperationsDuringLastMinute { get; private set; }
async void RegisterOperation()
{
    OperationsDuringLastMinute++;
    await Task.Delay(timeout);
    OperationsDuringLastMinute--;
}

В конце каждой операции вызывайте RegisterOperation (в главном потоке).

Если у вас не UI-контекст, то, как подсказывает @Raider в комментариях, вам понадобятся атомарные операции.

long operationsDuringLastMinute;
long OperationsDuringLastMinute
{
    get { return Interlocked.Read(ref operationsDuringLastMinute); }
}
async void RegisterOperation()
{
    Interlocked.Increment(ref operationsDuringLastMinute);
    await Task.Delay(timeout);
    Interlocked.Decrement(ref operationsDuringLastMinute);
}
Answer 2

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

Таймер здесь не нужен: во-первых, для него берется еще один (не вдаваясь в подробности) ненужный поток из thread pool, во-вторых, его точность уж совсем хромает, и не для того он был создан.

Для замеров точных меток времени в .NET есть класс Stopwatch, но так как ваша задача - посчитать количество операций за последнюю минуту, то переменной с предыдущим замером в сочетании с Thread.Sleep будет достаточно. При желании, Stopwatchи Thread.Sleep можно скомбинировать для получения более точного результата.

Для наглядности, я использовал квантование по секунде, но интервал легко меняется параметром метода:

public static class Program
{
    private static readonly ConcurrentQueue<string> Results = new ConcurrentQueue<string>();
    static void Main(string[] args)
    {
        Task work = Task.Run(new Action(DoWork));
        MeasureRate(work, Results);
        Console.WriteLine("Completed!");
    }
    private static void MeasureRate<T>(IAsyncResult work, IReadOnlyCollection<T> results, int timing = 1000 /* 1 second */)
    {
        int previousCount = 0;
        do
        {
            Console.WriteLine("Current rate: {0} entries per second", results.Count - previousCount);
            previousCount = results.Count;
            Thread.Sleep(timing);
        } while (!work.IsCompleted);
    }
    private static void DoWork()
    {
        var randomizer = new Random();
        for (int i = 0; i < 100000; i++)
        {
            Thread.Sleep(randomizer.Next(100));
            Results.Enqueue("Result " + i);
        }
    }
}
READ ALSO
mvvm обновление живого списка в View

mvvm обновление живого списка в View

Внутри модели есть живой список устройств, у каждого устройства есть свои поляКак оповещать список в VM, чтоб тот обновил View?

128
Как сверстать таблицу с разной шириной ячеек?

Как сверстать таблицу с разной шириной ячеек?

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

203
Что за рамка? Как его удалить с CSS? [требует правки]

Что за рамка? Как его удалить с CSS? [требует правки]

Что за рамка и как его удалить (или заменить именной цвет на другое посредством JS), при указании свойства width: 100% для изображения появляется...

173