Как гарантировать, что временная метка всегда уникальна?

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

У меня есть некоторые мысли, но ничто не поражает меня как "лучшее" решение этого. Есть ли способ, который я могу написать, который гарантирует, что каждый последующий вызов создает уникальный DateTime?

Должен ли я использовать для этого другой тип, возможно, длинный int? DateTime имеет очевидное преимущество: легко интерпретироваться как реальное время, в отличие, скажем, от инкрементного счетчика.

Обновление:. Здесь я закончил кодирование как простое компромиссное решение, которое по-прежнему позволяет использовать DateTime в качестве моего временного ключа, обеспечивая при этом уникальность при каждом вызове метода:

private static long _lastTime; // records the 64-bit tick value of the last time
private static object _timeLock = new object();

internal static DateTime GetCurrentTime() {
    lock ( _timeLock ) { // prevent concurrent access to ensure uniqueness
        DateTime result = DateTime.UtcNow;
        if ( result.Ticks <= _lastTime )
            result = new DateTime( _lastTime + 1 );
        _lastTime = result.Ticks;
        return result;
    }
}

Поскольку каждое значение галочки составляет только одну 10-миллионную секунду, этот метод вводит заметное перекос часов при вызове порядка 10 миллионов раз в секунду (что, кстати, достаточно эффективно для выполнения), что означает, что это вполне приемлемо для моих целей.

Вот несколько тестовых кодов:

DateTime start = DateTime.UtcNow;
DateTime prev = Kernel.GetCurrentTime();
Debug.WriteLine( "Start time : " + start.TimeOfDay );
Debug.WriteLine( "Start value: " + prev.TimeOfDay );
for ( int i = 0; i < 10000000; i++ ) {
    var now = Kernel.GetCurrentTime();
    Debug.Assert( now > prev ); // no failures here!
    prev = now;
}
DateTime end = DateTime.UtcNow;
Debug.WriteLine( "End time:    " + end.TimeOfDay );
Debug.WriteLine( "End value:   " + prev.TimeOfDay );
Debug.WriteLine( "Skew:        " + ( prev - end ) );
Debug.WriteLine( "GetCurrentTime test completed in: " + ( end - start ) );

... и результаты:

Start time:  15:44:07.3405024
Start value: 15:44:07.3405024
End time:    15:44:07.8355307
End value:   15:44:08.3417124
Skew:        00:00:00.5061817
GetCurrentTime test completed in: 00:00:00.4950283

Иными словами, через полсекунды он создал 10 миллионов уникальных временных меток, и конечный результат был только продвинут на полсекунды. В реальных приложениях перекос был бы незаметным.

Ответ 1

Один из способов получения строго возрастающей последовательности временных меток без дубликатов - следующий код.

По сравнению с другими ответами здесь у этого есть следующие преимущества:

  1. Значения тесно связаны с фактическими значениями в реальном времени (за исключением экстремальных обстоятельств с очень высокой частотой запросов, когда они будут немного опережать реальное время).
  2. Он блокируется и должен работать лучше, чем решения, использующие операторы lock.
  3. Это гарантирует возрастающий порядок (простое добавление циклического счетчика не делает).

public class HiResDateTime
{
   private static long lastTimeStamp = DateTime.UtcNow.Ticks;
   public static long UtcNowTicks
   {
       get
       {
           long original, newValue;
           do
           {
               original = lastTimeStamp;
               long now = DateTime.UtcNow.Ticks;
               newValue = Math.Max(now, original + 1);
           } while (Interlocked.CompareExchange
                        (ref lastTimeStamp, newValue, original) != original);

           return newValue;
       }
   }
}

Ответ 2

Er, ответ на ваш вопрос заключается в том, что "вы не можете", так как если одновременно будут выполняться две операции (что они будут в многоядерных процессорах), они будут иметь одинаковую метку времени, независимо от того, какая точность вам удается собраться.

Тем не менее, похоже, что вы хотите, это какой-то автоинкрементный поточно-безопасный счетчик. Чтобы реализовать это (предположительно, как глобальный сервис, возможно, в статическом классе), вы должны использовать метод Interlocked.Increment, и если вы решили необходимо больше, чем int.MaxValue возможных версий, а также Interlocked.Read.

Ответ 3

DateTime.Now обновляется только каждые 10-15 мс.

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

Как получить временную метку точности тиков в .NET/С#?

При этом: метки времени - ужасные ключи для информации; если вещи происходят так быстро, вы можете захотеть индекс/счетчик, который сохраняет дискретный порядок элементов по мере их появления. Там нет никакой двусмысленности там.

Ответ 4

Я считаю, что самый надежный способ - объединить метку времени и атомный счетчик. Вы уже знаете проблему с плохим разрешением временной метки. Использование атомарного счетчика само по себе также имеет простую проблему, требующую сохранения своего состояния, если вы собираетесь остановить и запустить приложение (в противном случае счетчик возвращается к 0, вызывая дубликаты).

Если вы просто собираетесь использовать уникальный идентификатор, это будет так же просто, как объединить метку времени и значение счетчика с разделителем между ними. Но поскольку вы хотите, чтобы значения всегда были в порядке, этого будет недостаточно. В основном все, что вам нужно сделать, это использовать значение счетчика атомов, чтобы добавить точность фиксированной ширины добавленной точности в метку времени. Я разработчик Java, поэтому я пока не смогу предоставить образец кода С#, но проблема в обоих доменах одинакова. Поэтому просто следуйте этим общим шагам:

  • Вам понадобится метод, который даст вам значения счетчика, циклически изменяющиеся от 0 до99999. 100000 - это максимальное количество возможных значений при объединении миллисекундной метки времени с фиксированным значением ширины в 64-битной длине. Таким образом, вы в основном предполагаете, что вам не понадобится более 100 000 идентификаторов в пределах одного временного разрешения (15 мсек или около того). Статический метод, использующий класс Interlocked для обеспечения атомарного прироста и возврата к 0, является идеальным способом.
  • Теперь, чтобы сгенерировать ваш идентификатор, вы просто объединяете свою метку времени со значением вашего счетчика, дополненным 5 символами. Поэтому, если ваша метка времени была 13023991070123, а ваш счетчик был в 234, идентификатор будет равен 1302399107012300234.

Эта стратегия будет работать до тех пор, пока вам не нужны идентификаторы быстрее, чем 6666 в мс (при условии, что 15 мс - это наиболее гранулированное разрешение) и всегда будет работать без необходимости сохранять какое-либо состояние в перезапуске вашего приложения.

Ответ 5

Нельзя гарантировать, что он будет уникальным, но, возможно, использует ticks достаточно подробно?

Один тик представляет собой сто наносекунды или одну десятимиллионную второй. 10 000 тиков в миллисекунды.

Ответ 6

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