C# 系统正常运行时间&;记忆载体
我需要一种健壮的方法来获得系统正常运行时间,并最终使用了以下内容。 添加了一些注释以帮助人们阅读。我不能使用任务,因为它必须在.NET 3.5应用程序上运行C# 系统正常运行时间&;记忆载体,c#,multithreading,memory-barriers,C#,Multithreading,Memory Barriers,我需要一种健壮的方法来获得系统正常运行时间,并最终使用了以下内容。 添加了一些注释以帮助人们阅读。我不能使用任务,因为它必须在.NET 3.5应用程序上运行 // This is a structure, can't be marked as volatile // need to implement MemoryBarrier manually as appropriate private static TimeSpan _uptime; private static TimeSpan Ge
// This is a structure, can't be marked as volatile
// need to implement MemoryBarrier manually as appropriate
private static TimeSpan _uptime;
private static TimeSpan GetUptime()
{
// Try and set the Uptime using per counters
var uptimeThread = new Thread(GetPerformanceCounterUptime);
uptimeThread.Start();
// If our thread hasn't finished in 5 seconds, perf counters are broken
if (!uptimeThread.Join(5 * 1000))
{
// Kill the thread and use Environment.TickCount
uptimeThread.Abort();
_uptime = TimeSpan.FromMilliseconds(
Environment.TickCount & Int32.MaxValue);
}
Thread.MemoryBarrier();
return _uptime;
}
// This sets the System uptime using the perf counters
// this gives the best result but on a system with corrupt perf counters
// it can freeze
private static void GetPerformanceCounterUptime()
{
using (var uptime = new PerformanceCounter("System", "System Up Time"))
{
uptime.NextValue();
_uptime = TimeSpan.FromSeconds(uptime.NextValue());
}
}
我正在努力解决的问题是Thread.MemoryBarrier()
应该放在哪里?
我在读取值之前放置它,但是当前线程或其他线程可能已经写入了它。上面的说法正确吗
编辑,根据丹尼尔回答
这就是我所要实现的,谢谢你们两位的洞察力
private static TimeSpan _uptime;
private static TimeSpan GetUptime()
{
var uptimeThread = new Thread(GetPerformanceCounterUptime);
uptimeThread.Start();
if (uptimeThread.Join(5*1000))
{
return _uptime;
}
else
{
uptimeThread.Abort();
return TimeSpan.FromMilliseconds(
Environment.TickCount & Int32.MaxValue);
}
}
private static void GetPerformanceCounterUptime()
{
using (var uptime = new PerformanceCounter("System", "System Up Time"))
{
uptime.NextValue();
_uptime = TimeSpan.FromSeconds(uptime.NextValue());
}
}
编辑2
根据Bob的评论进行更新
private static DateTimeOffset _uptime;
private static DateTimeOffset GetUptime()
{
var uptimeThread = new Thread(GetPerformanceCounterUptime);
uptimeThread.Start();
if (uptimeThread.Join(5*1000))
{
return _uptime;
}
else
{
uptimeThread.Abort();
return DateTimeOffset.Now.Subtract(TimeSpan.FromMilliseconds(
Environment.TickCount & Int32.MaxValue));
}
}
private static void GetPerformanceCounterUptime()
{
if (_uptime != default(DateTimeOffset))
{
return;
}
using (var uptime = new PerformanceCounter("System", "System Up Time"))
{
uptime.NextValue();
_uptime = DateTimeOffset.Now.Subtract(
TimeSpan.FromSeconds(uptime.NextValue()));
}
}
Thread.Join
已确保uptimeThread执行的写入在主线程上可见。你不需要任何明确的记忆障碍。(如果没有由Join
执行的同步,则在写入之后和读取之前,两个线程上都需要屏障)
但是,您的代码存在一个潜在的问题:写入TimeSpan
结构不是原子的,主线程和uptimeThread可能会同时写入它(thread.Abort
只是发出中止信号,但不等待线程完成中止),导致写入中断。
我的解决方案是在中止时根本不使用该字段。此外,对GetUptime()
的多个并发调用可能会导致相同的问题,因此您应该使用实例字段
private static TimeSpan GetUptime()
{
// Try and set the Uptime using per counters
var helper = new Helper();
var uptimeThread = new Thread(helper.GetPerformanceCounterUptime);
uptimeThread.Start();
// If our thread hasn't finished in 5 seconds, perf counters are broken
if (uptimeThread.Join(5 * 1000))
{
return helper._uptime;
} else {
// Kill the thread and use Environment.TickCount
uptimeThread.Abort();
return TimeSpan.FromMilliseconds(
Environment.TickCount & Int32.MaxValue);
}
}
class Helper
{
internal TimeSpan _uptime;
// This sets the System uptime using the perf counters
// this gives the best result but on a system with corrupt perf counters
// it can freeze
internal void GetPerformanceCounterUptime()
{
using (var uptime = new PerformanceCounter("System", "System Up Time"))
{
uptime.NextValue();
_uptime = TimeSpan.FromSeconds(uptime.NextValue());
}
}
}
但是,我不确定中止性能计数器线程是否能正常工作-
thread.Abort()
只中止托管代码的执行。如果代码挂起在Windows API调用中,线程将继续运行。AFAIK在.NET中的写入是不稳定的,因此您需要内存隔离的唯一位置是在每次读取之前,因为它们需要重新排序和/或缓存。引自:
以下是我理解的规则,仅供参考
尽可能简单地说:
Rule 1: Data dependence among loads and stores is never violated.
Rule 2: All stores have release semantics, i.e. no load or store may move after one.
Rule 3: All volatile loads are acquire, i.e. no load or store may move before one.
Rule 4: No loads and stores may ever cross a full-barrier.
Rule 5: Loads and stores to the heap may never be introduced.
Rule 6: Loads and stores may only be deleted when coalescing adjacent loads and
stores from/to the same location.
请注意,根据此定义,非易失性负载不需要
有任何与之相关的障碍。因此,负载可以自由移动
重新排序,写入可能会在它们之后移动(尽管不是在之前,因为
规则2)。有了这个模型,您真正需要的唯一真实案例
规则4规定的完整屏障的强度是为了防止
在存储之后是易失性负载的情况下重新排序。
如果没有屏障,说明可能会重新排序
你能解释一下为什么Thread.Join()会保证一个处理器上完成的写入被另一个处理器的读取看到吗?同意问题是字段,如果没有.NET 3.5要求,我会将其作为任务实现。@M Afifi:
Thread.Join
在调用时带有隐式内存障碍。通常,等待操作(例如线程退出)在另一个线程上发生的操作,还要确保在等待完成后,操作之前发生的所有内存写入都可见。“等到X完成”如果不是意味着“等到我看到X的结果”的话,那将是非常无用的。不幸的是,Microsoft.NET API文档没有明确提到内存顺序效应。(Java文档在这方面要好得多)存储在.NET CLR实现上是隐式易变的(您发布的规则不受规范的保证!)在这里没有帮助,因为存储不是原子的。@Daniel:因为OP讨论的是易失性/内存围栏,我觉得他只是在问可见性,而不是原子性。问的问题是“这个代码正确吗?”,没有原子性的可见性在那里是无用的。你是说性能计数器会在应用程序的生命周期内损坏吗?这就是为什么你每次都要开始检查新的线?@Bobb,嗯,我不确定。我的应用程序是在机器启动时启动的windows服务。我看到它已损坏,我的应用程序始终运行。在某些机器上,它很可能在应用程序的生命周期内发生。PS,我很感激我可以做一些缓存,添加DateTime。现在作为一个成员变量,如果它们都不是null,则计算新的正常运行时间值。可能会更改代码以确保不会产生潜在负载的“不可分配”线程。创建和启动线程只是为了获取计时器值是错误的,我甚至不知道从哪里开始。你应该以某种方式在开始时拍摄一次正常运行时间的快照(我不能评论你的“腐败”概念)和使用高分辨率时钟的本地时间(谷歌上有很多例子)。在每个GetUpTime上,只需返回initialUpTime+(timeNow-timeAtUpTimeSnapshot)。这个调用只需要几行代码,是同步的、可预测的和可管理的。。。你现在所做的毫无意义。@Bobb我同意每次开始一根线都是懒惰的。酌情更新。但是第一次运行时我确实需要一个单独的线程。尽管令人钦佩,正常运行时间是误导性的,更像是上次启动时间。只需锁定正常运行时间(内部使用2行代码)。你不需要记忆障碍。