2012-06-19 7 views
4

Мне нужен надежный способ получить бесперебойную работу системы и в конечном итоге использовать что-то следующее. Добавлены комментарии, чтобы помочь людям прочитать его. Я не могу использовать Task, поскольку это должно выполняться в приложении .NET 3.5.Система Uptime & MemoryBarrier

// 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()? Я размещаю его перед чтением значения, но либо текущий поток, либо другой поток мог бы записать на него. Правильно ли это выглядит?

Edit, ответ, основанный на Daniel

Это то, что я eneded до реализации, спасибо вам обоим за понимание.

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()); 
    } 
} 

Edit 2

обновляется на основе комментариев Боба.

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())); 
    } 
} 
+0

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

+0

@Bobb, ммм не уверен. Мое приложение - это служба Windows, которая запускается при запуске машины. Я видел, что он поврежден, и мое приложение всегда работает. Вероятно, это произойдет в течение всего срока службы приложения на некоторых машинах. PS, я ценю, что я могу сделать некоторое кэширование, добавить DateTime.Now в качестве переменной-члена, и если они оба не нуль вычисляют новое значение времени безотказной работы. Вероятно, изменит код, чтобы сделать это, чтобы гарантировать, что я не создаю потенциальную нагрузку «неаборируемых» потоков. –

+0

Создание и запуск потока, чтобы получить значение таймера, настолько ошибочно, что я даже не знаю, с чего начать. вы должны сделать снимок времени безотказной работы как-то в начале (я не могу прокомментировать ваше понятие «коррупция») и местное время, используя часы привет-res (много примеров в google). и на каждом GetUpTime вы просто возвращаете initialUpTime + (timeNow - timeAtUpTimeSnapshot). этот вызов займет несколько строк кода, будет синхронным, предсказуемым и управляемым ... то, что вы сейчас делаете, не имеет никакого смысла. –

ответ

2

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, поток будет продолжать работать.

+0

не могли бы вы пояснить, почему Thread.Join() гарантировал бы, что записи, выполненные на одном процессоре, видны при чтении другого? Согласившись с проблемой, являющейся полем, я бы выполнил задачу , если бы не требование .NET 3.5. –

+0

@M Afifi: 'Thread.Join' несет неявный барьер памяти при вызове. – Tudor

+0

Как правило, операции, ожидающие действия (например, выход потока), происходят в другом потоке, также гарантируют, что все записи в памяти, которые произошли до действия, будут видны после завершения ожидания. «Подождите, пока X не будет сделано» было бы бесполезно, если бы это не означало «Подождите, пока я не увижу результаты X». К сожалению, в документации Microsoft .NET API явно не упоминаются эффекты порядка памяти. (документация на Java намного лучше в этом отношении) – Daniel

2

AFAIK пишет, что в .NET неустойчивы, поэтому единственное место, где вам понадобится забор памяти, будет перед каждым чтением, поскольку они подлежат переупорядочению и/или кешированию. Цитирую a post by Joe Duffy:

Для справки, вот правила, как я пришел к пониманию их сказал так просто, как я могу:

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, заключается в том, чтобы предотвратить переупорядочение в случае, когда за магазином следует летучая нагрузка. Без барьера инструкции могут быть изменены.

+0

Магазин, который неявно волатилен в реализации .NET CLR (правила, которые вы опубликовали, не гарантированы спецификацией!) здесь не помогают, поскольку хранилище не является атомарным. – Daniel

+0

@ Daniel: Поскольку OP обсуждал волатильные/запоминающие заборы, у меня создалось впечатление, что он только спрашивает о видимости, а не о атомичности. – Tudor

+0

Вопрос: «Правильно ли этот код?», И видимость без атомарности здесь бесполезна. – Daniel