2008-08-12 20 views
14

Próbuję odbudować starą aplikację metronomu, która została oryginalnie napisana przy użyciu MFC w C++ i napisana w .NET przy użyciu C#. Jednym z problemów, na które napotykam, jest uzyskanie odliczającego czasu, aby "zaznaczyć" wystarczająco dokładnie.Uzyskiwanie dokładnych odgłosów z timera w C#

Na przykład, zakładając łatwy BPM (uderzeń na minutę) o wartości 120, timer powinien odhaczać co 0,5 sekundy (lub 500 milisekund). Używanie tego jako podstawy dla tyknięć nie jest jednak do końca dokładne, ponieważ .NET gwarantuje tylko, że czasomierz nie zaznaczy się przed upływem czasu.

Obecnie, aby obejść ten problem z tego samego przykładu 120 BPM używanego powyżej, ustawiam tyknięcia na około 100 milisekund i odtwarzam dźwięk kliknięcia na każdym piątym teście zegara. To znacznie poprawia dokładność, ale jeśli wydaje się trochę hackem.

Jaki jest najlepszy sposób na uzyskanie dokładnych odcisków? Wiem, że dostępnych jest więcej timerów niż timery formularzy systemu Windows, które są łatwo dostępne w Visual Studio, ale nie jestem do końca obeznany z nimi.

Odpowiedz

9

Istnieją trzy klasy zegarów o nazwie "Timer" w .NET. Wygląda na to, że używasz Windows Forms, ale w rzeczywistości klasa System.Threading.Timer może być bardziej przydatna - ale bądź ostrożny, ponieważ wywołuje ponownie wątek puli, więc nie możesz bezpośrednio wchodzić w interakcję z formularzem. wywołanie zwrotne.

Innym rozwiązaniem może być p/wywołania multimedialnego do liczników Win32 - timeGetTime, timeSetPeriod itp

Szybkie pomocną znaleźć to, co może być przydatne http://www.codeproject.com/KB/miscctrl/lescsmultimediatimer.aspx

'multimedia' (zegar) jest słowo buzz do wyszukania w tym kontekście.

1

Do czego służy aplikacja C++? Zawsze możesz użyć tej samej rzeczy lub zawinąć kod timera z C++ na klasę C++/CLI.

0

Klasy czasomierza mogą zacząć się dziwnie zachowywać, gdy kod zdarzenia "Tikowanie" nie zakończy się do czasu pojawienia się następnego "tiknięcia". Jednym ze sposobów zwalczania tego jest wyłączenie timera na początku zdarzenia tick, a następnie ponowne włączenie go na końcu.

Jednak takie podejście nie jest odpowiednie w przypadkach, w których czas wykonania kodu "tick" jest niedopuszczalnym błędem w takcie znacznika czasu, ponieważ zegar będzie wyłączony (nie licząc) w tym czasie.

Jeśli wyłączenie timera jest opcją, a następnie można również osiągnąć ten sam efekt, tworząc osobny wątek, który wykonuje, śpi na x milisekund, wykonuje, śpi, itp ...

+0

Ale wtedy można mieć tylko pewność, że wątek śpi co najmniej X millioseconds; program planujący wątek nie poświadcza, że ​​wątek będzie działał z dokładnością do miliona metrów. – Wilhelm

+0

Prawa. Zgadzam się z tym, co powiedziałeś, że nie potrafisz określić czasu następnego tyknięcia. Celem tego, co mówię, jest to, że nie chcesz wykonywać kodu zdarzenia od poprzedniego tiku, gdy pojawi się następny tik. –

0

System.Windows.Forms.Timer jest ograniczona do dokładność 55 milisekund ...

+1

Czy jest na niej oficjalna dokumentacja? – MajesticRa

+0

@MajesticRa: http://msdn.microsoft.com/en-us/library/system.windows.forms.timer.aspx Zobacz tekst w żółtym polu –

+0

Dziękujemy! Kiedy to napisałem, nie zauważyłem, że yazanpro oznacza właśnie zegar Forms. I myślał, że on mówi o zegarach w ogóle. Ale bardzo dziękuję za odpowiedź! – MajesticRa

1

Miałem ten problem podczas opracowywania niedawnego projektu rejestrowania danych. Problem z licznikami czasu .NET (windows.forms, system.threading i system.timer) polega na tym, że są one dokładne tylko do około 10 milli sekund, co jest spowodowane wbudowanym planowaniem zdarzeń wbudowanym w platformę .NET. (Mówię tutaj o .NET 2). To było dla mnie nie do przyjęcia, więc musiałem użyć timera multimedialnego (musisz zaimportować bibliotekę dll). Napisałem też klasę wrapperów dla wszystkich timerów, więc możesz przełączać się między nimi, jeśli to konieczne, przy minimalnych zmianach kodu.Sprawdź moje blogu tutaj: http://www.indigo79.net/archives/27

1

Inną możliwością jest to, że jest to błąd w realizacji WPF z DispatcherTimer (występuje niezgodność między milisekund i kleszczy powodując potencjalne niedokładności w zależności od dokładnego czasu realizacji procesu), co widać poniżej :

http://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/DispatcherTimer.cs,143

class DispatcherTimer 
{ 
    public TimeSpan Interval 
    { 
     set 
     { 
      ... 
      _interval = value; 
      // Notice below bug: ticks1 + milliseconds [Bug1] 
      _dueTimeInTicks = Environment.TickCount + (int)_interval.TotalMilliseconds; 
     } 
    } 
} 

http://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/Dispatcher.cs

class Dispatcher 
{ 
    private object UpdateWin32TimerFromDispatcherThread(object unused) 
    { 
     ... 
     _dueTimeInTicks = timer._dueTimeInTicks; 
     SetWin32Timer(_dueTimeInTicks); 
    } 

    private void SetWin32Timer(int dueTimeInTicks) 
    { 
     ... 
     // Notice below bug: (ticks1 + milliseconds) - ticks2 [Bug2 - almost cancels Bug1, delta is mostly milliseconds not ticks] 
     int delta = dueTimeInTicks - Environment.TickCount; 
     SafeNativeMethods.SetTimer( 
      new HandleRef(this, _window.Value.Handle), 
      TIMERID_TIMERS, 
      delta); // <-- [Bug3 - if delta is ticks, it should be divided by TimeSpan.TicksPerMillisecond = 10000] 
    } 
} 

http://referencesource.microsoft.com/#WindowsBase/Shared/MS/Win32/SafeNativeMethodsCLR.cs,505

class SafeNativeMethodsPrivate 
{ 
    ... 
    [DllImport(ExternDll.User32, SetLastError = true, ExactSpelling=true, CharSet=System.Runtime.InteropServices.CharSet.Auto)] 
    public static extern IntPtr SetTimer(HandleRef hWnd, int nIDEvent, int uElapse, NativeMethods.TimerProc lpTimerFunc); 
} 

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644906%28v=vs.85%29.aspx

uElapse [in] 
Type: UINT 
The time-out value, in milliseconds. // <-- milliseconds were needed eventually