首页 > 解决方案 > 我怎样才能使这个 DateTime.Now.ToFileTimeUtc() 成为线程安全的?

问题描述

我想使用 DateTime.Now.ToFileTimeUtc() 生成文件名,但是使用多线程我为多个线程获得相同的文件名,这导致写入文件的 I/O 错误。我想让每个线程都有一个单独的文件。

我如何在 C# 中使用 DateTime.Now.ToFileTimeUtc() 获得不同的文件名?

标签: c#multithreadingdatetimefile-handling

解决方案


如果您在紧密循环中打印出 的值DateTime.Now.ToFileTimeUtc(),您将看到如下结果:

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

这告诉我们两件事:

  1. 返回值有时会重复。
  2. 值之间的最小间隔大约超过 1,000(以 100ns 为单位测量)。

由于时间的实际精度远小于时间的精度单位,我们可以安全地解决这个问题,如下所示:

首先,创建一个调整整数,初始化为零。然后:

  1. 将文件时间值转换为 10,000 的倍数。这不会失去显着的精度。
  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 个线程并在每个线程的紧密循环中获得独特的时间,这确实给它带来了压力。


推荐阅读