Pergunta

Estou tentando reconstruir um aplicativo metrônomo antigo que foi originalmente escrito usando MFC em C++ para ser escrito em .NET usando C#.Um dos problemas que estou enfrentando é fazer com que o cronômetro "marque" com precisão suficiente.

Por exemplo, assumindo um BPM fácil (batidas por minuto) de 120, o cronômetro deve marcar a cada 0,5 segundos (ou 500 milissegundos).Usar isso como base para os tiques, no entanto, não é totalmente preciso, pois o .NET apenas garante que o seu cronômetro não funcionará antes que o tempo decorrido tenha passado.

Atualmente, para contornar isso no mesmo exemplo de 120 BPM usado acima, estou configurando os tiques para algo em torno de 100 milissegundos e reproduzindo apenas o som do clique a cada 5 tiques do cronômetro.Isso melhora um pouco a precisão, mas parece um hack.

Então, qual é a melhor maneira de obter ticks precisos?Eu sei que há mais temporizadores disponíveis do que o temporizador de formulários do Windows que está disponível no Visual Studio, mas não estou muito familiarizado com eles.

Foi útil?

Solução

Existem três classes de timer chamadas 'Timer' no .NET.Parece que você está usando o Windows Forms, mas na verdade você pode achar a classe System.Threading.Timer mais útil - mas tenha cuidado porque ela chama de volta em um thread de pool, então você não pode interagir diretamente com seu formulário de o retorno de chamada.

Outra abordagem pode ser invocar os temporizadores multimídia Win32 - timeGetTime, timeSetPeriod, etc.

Um rápido Google encontrou isso, o que pode ser útil http://www.codeproject.com/KB/miscctrl/lescsmultimediatimer.aspx

'Multimídia' (temporizador) é a palavra da moda a ser pesquisada neste contexto.

Outras dicas

O que o aplicativo C++ está usando?Você sempre pode usar a mesma coisa ou agrupar o código do timer de C++ em uma classe C++/CLI.

Eu tive esse problema ao desenvolver um projeto recente de registro de dados.O problema com os temporizadores .NET ( windows.forms, system.threading e system.timer ) é que eles só têm precisão de cerca de 10 milissegundos, devido ao agendamento de eventos incorporado ao .NET, acredito.(Estou falando sobre .NET 2 aqui).Isso não era aceitável para mim e então tive que usar o timer multimídia (você precisa importar a dll).Também escrevi uma classe wrapper para todos os temporizadores e para que você possa alternar entre eles, se necessário, usando alterações mínimas de código.Confira minha postagem no blog aqui:http://www.indigo79.net/archives/27

Outra possibilidade é que exista um bug na implementação do WPF do DispatcheTerMer (existe uma incompatibilidade entre milissegundos e carrapatos, causando uma potencial imprecisão, dependendo do tempo exato de execução do processo), como evidenciado abaixo:

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

As classes de timer podem começar a se comportar de maneira estranha quando o código do evento 'tick' do timer não termina de ser executado no momento em que ocorre o próximo 'tick'.Uma maneira de combater isso é desabilitar o cronômetro no início do evento tick e reativá-lo no final.

Porém, esta abordagem não é adequada nos casos em que o tempo de execução do código 'tick' não é um erro aceitável no tempo do tick, uma vez que o cronômetro ficará desabilitado (sem contar) durante esse tempo.

Se desabilitar o cronômetro for uma opção, você também poderá obter o mesmo efeito criando um thread separado que executa, dorme por x milissegundos, executa, dorme, etc.

System.Windows.Forms.Timer está limitado a uma precisão de 55 milissegundos...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top