我怎样才能使这个 DateTime.Now.ToFileTimeUtc() 成为线程安全的呢?
How could I make this DateTime.Now.ToFileTimeUtc() a thread safe one?
我想使用 DateTime.Now.ToFileTimeUtc() 生成文件名,但是对于多线程,我为多个线程获取相同的文件名,这导致写入文件时出现 I/O 错误。
我想让每个线程都有一个单独的文件。
如何在 C# 中使用 DateTime.Now.ToFileTimeUtc() 获取不同的文件名?
您可以将函数包装在另一个函数中,并使用信号量等待 filename/datatime。
C#
https://docs.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
如果你在一个紧密的循环中打印出 DateTime.Now.ToFileTimeUtc()
的值,你会看到这样的结果:
132453421456289289
132453421456289289
132453421456305151
132453421456312499
132453421456312499
132453421456312499
132453421456322499
132453421456322499
132453421456332746
132453421456342443
132453421456342443
132453421456352425
132453421456352425
132453421456362391
这告诉我们两件事:
- 返回值有时会重复。
- 值之间的最小间隔大约超过 1,000(以 100ns 为单位测量)。
由于时间的实际精度远小于时间的精度单位,我们可以安全地解决这个问题,如下所示:
首先,创建一个调整整数,初始化为零。那么:
- 将文件时间值转换为 10,000 的倍数。这不
失去显着精度。
- 如果要返回的文件时间与之前的时间相同,则增加调整。
- 否则,将调整设置回零。
例如:
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()
returns 一个以 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 个线程并在每个线程的紧密循环中获得唯一时间,这确实给它带来了压力。
我想使用 DateTime.Now.ToFileTimeUtc() 生成文件名,但是对于多线程,我为多个线程获取相同的文件名,这导致写入文件时出现 I/O 错误。 我想让每个线程都有一个单独的文件。
如何在 C# 中使用 DateTime.Now.ToFileTimeUtc() 获取不同的文件名?
您可以将函数包装在另一个函数中,并使用信号量等待 filename/datatime。
C# https://docs.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
如果你在一个紧密的循环中打印出 DateTime.Now.ToFileTimeUtc()
的值,你会看到这样的结果:
132453421456289289
132453421456289289
132453421456305151
132453421456312499
132453421456312499
132453421456312499
132453421456322499
132453421456322499
132453421456332746
132453421456342443
132453421456342443
132453421456352425
132453421456352425
132453421456362391
这告诉我们两件事:
- 返回值有时会重复。
- 值之间的最小间隔大约超过 1,000(以 100ns 为单位测量)。
由于时间的实际精度远小于时间的精度单位,我们可以安全地解决这个问题,如下所示:
首先,创建一个调整整数,初始化为零。那么:
- 将文件时间值转换为 10,000 的倍数。这不 失去显着精度。
- 如果要返回的文件时间与之前的时间相同,则增加调整。
- 否则,将调整设置回零。
例如:
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()
returns 一个以 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 个线程并在每个线程的紧密循环中获得唯一时间,这确实给它带来了压力。