Как я мог бы сделать это DateTime.Now .ToFileTimeUtc() потокобезопасным?

#c# #multithreading #datetime #file-handling

#c# #многопоточность #datetime #обработка файлов

Вопрос:

Я хочу сгенерировать имена файлов, используя DateTime.Now.ToFileTimeUtc() но при многопоточности я получаю одно и то же имя файла для нескольких потоков, что приводит к ошибке ввода-вывода при записи файла. Я хочу, чтобы у каждого потока был отдельный файл.

Как я мог бы добиться получения разных имен файлов с помощью DateTime.Now.ToFileTimeUtc() в C #?

Комментарии:

1. Вероятно, вам не нужно несколько потоков, чтобы все пошло не так — один поток, вызывающий его достаточно быстро, может привести к сбою. Однако, игнорируя это: допустимо ли добавлять аффикс к имени файла или оно должно быть просто длинным?

2. Обратите внимание, что DateTime. Now (как правило) обновляется примерно раз в 50 мс, так что это окно, в котором два потока могут видеть одно и то же время.

3. Не разрешается добавлять аффикс и следует использовать только длинное значение, даже получение значения datetime путем блокировки объекта получает одно и то же значение для более чем одного потока

4. @VaseemAkram это определенно проблема XY. Вы предполагаете , что можете использовать DateTime.Now для генерации уникальных имен файлов, когда эта функция будет генерировать дубликаты, даже если она вызывается одним потоком. Время, в конце концов, не заботится о потоках конкретной машины, оно одинаково для everone (по крайней мере, в соответствии с нашим текущим пониманием). Если вы попытаетесь использовать любую глобальную переменную состояния, вы получите дубликаты из нескольких потоков. Это не проблема потокобезопасности. Значение такое, какое оно есть

5. @VaseemAkram в чем ваша реальная проблема? Что ты пытаешься сделать? Даже если вы использовали высокопроизводительный счетчик тиков процессора, вы все равно могли бы получить дубликаты, если бы два потока запрашивали его одновременно. Библиотеки ведения журнала решают эту проблему, используя Interlocked.Increment атомарное увеличение и чтение глобального счетчика за один и тот же атомарный шаг

Ответ №1:

Если вы распечатаете значения DateTime.Now.ToFileTimeUtc() в замкнутом цикле, вы увидите такие результаты:

 132453421456289289
132453421456289289
132453421456305151
132453421456312499
132453421456312499
132453421456312499
132453421456322499
132453421456322499
132453421456332746
132453421456342443
132453421456342443
132453421456352425
132453421456352425
132453421456362391
  

Это говорит нам о двух вещах:

  1. Возвращаемые значения иногда повторяются.
  2. Наименьший интервал между значениями составляет около 1000 (измеряется в единицах 100ns).

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

Во-первых, создайте корректирующее целое число, инициализированное нулем. Затем:

  1. Преобразуйте значение времени файла в кратное 10000. Это не теряет значительной точности.
  2. Если время возврата файла совпадает с предыдущим временем, увеличьте корректировку.
  3. В противном случае установите настройку обратно на ноль.

Например:

 public static class UniqueFileTime
{
    public static long Generate()
    {
        long next = 10_000 * (DateTime.Now.ToFileTimeUtc() / 10_000);

        lock (_lock)
        {
            next = Math.Max(next, _last);

            if (next == _last)
            {
                  _adj;

                if (_adj == 10_000) // Broken!
                    throw new InvalidOperationException("UniqueFileTime.Generate() called too often.");
            }
            else
            {
                _adj  = 0;
                _last = next;
            }

            return next   _adj;
        }
    }

    static long _last;
    static int  _adj;
    static readonly object _lock = new object();
}
  

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

Это означает, что точность времени файла составляет 10 000 единиц с точностью до 100 наносекунд (поскольку ToFileTimeUtc() возвращает значение в 100 наносекундных единицах). Это все еще точность в 1 миллисекунду — более чем достаточно для времени файла для этих целей.

Вот небольшая тестовая программа, чтобы подчеркнуть это:

 static class Program
{
    public static void Main()
    {
        var results = new List<List<long>>(1000);

        for (int i = 0; i < 8;   i)
            results.Add(new List<long>());

        Parallel.Invoke(
            () => getTimes(results[0]),
            () => getTimes(results[1]), 
            () => getTimes(results[2]),
            () => getTimes(results[3]),
            () => getTimes(results[4]),
            () => getTimes(results[5]),
            () => getTimes(results[6]),
            () => getTimes(results[7])
        );

        foreach (var time in results.SelectMany(r => r))
        {
            Console.WriteLine(time);
        }

        int distinctCount = results.SelectMany(r => r).Distinct().Count();

        if (distinctCount != 8000)
            Console.WriteLine("FAILED - Distinct should be 8000, but was "   distinctCount);
    }

    static void getTimes(List<long> times)
    {
        for (int i = 0; i < 1000;   i)
            times.Add(UniqueFileTime.Generate());
    }
}
  

Это действительно подчеркивает это, создавая 8 потоков и получая уникальные времена в узком цикле для каждого потока.

Ответ №2:

Вы можете обернуть функцию в другую функцию и использовать семафор для ожидания имени файла / даты.

C # https://learn.microsoft.com/en-us/dotnet/api/system.threading.semaphore?view=netcore-3.1

Java https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html

Комментарии:

1. Это не решит проблему OP. Речь идет совсем не о потокобезопасности. Даже один поток будет выдавать одинаковые значения, если вызывается в замкнутом цикле