Pergunta

Ambos System.Timers.Timer e System.Threading.Timer disparar em intervalos consideravelmente diferentes dos solicitados.Por exemplo:

new System.Timers.Timer(1000d / 20);

produz um temporizador que dispara 16 vezes por segundo, não 20.

Para ter certeza de que não há efeitos colaterais de manipuladores de eventos muito longos, escrevi este pequeno programa de teste:

int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

// Test System.Timers.Timer
foreach (int frequency in frequencies)
{
    int count = 0;

    // Initialize timer
    System.Timers.Timer timer = new System.Timers.Timer(1000d / frequency);
    timer.Elapsed += delegate { Interlocked.Increment(ref count); };

    // Count for 10 seconds
    DateTime start = DateTime.Now;
    timer.Enabled = true;
    while (DateTime.Now < start + TimeSpan.FromSeconds(10))
        Thread.Sleep(10);
    timer.Enabled = false;

    // Calculate actual frequency
    Console.WriteLine(
        "Requested frequency: {0}\nActual frequency: {1}\n",
        frequency, count / 10d);
}

A saída é semelhante a esta:

Requeridos:5Hz;real:4,8Hz
Requeridos:10Hz;real:9,1Hz
Requeridos:15Hz;real:12,7Hz
Requeridos:20 Hz;real:16Hz
Requeridos:30 Hz;real:21,3Hz
Requeridos:50 Hz;real:31,8Hz
Requeridos:75 Hz;real:63,9Hz
Requeridos:100Hz;real:63,8Hz
Requeridos:200Hz;real:63,9Hz
Requeridos:500Hz;real:63,9Hz

A frequência real diverge em até 36% da solicitada.(E evidentemente não pode exceder 64 Hz.) Dado que a Microsoft recomenda este temporizador pela sua "maior precisão" em relação a System.Windows.Forms.Timer, isso me intriga.

Aliás, esses não são desvios aleatórios.Eles são sempre os mesmos valores.E um programa de teste semelhante para a outra classe de temporizador, System.Threading.Timer, mostra exatamente os mesmos resultados.

No meu programa atual, preciso coletar medições precisamente a 50 amostras por segundo.Isto ainda não deve exigir um sistema em tempo real.E é muito frustrante obter 32 amostras por segundo em vez de 50.

Alguma ideia?

@Cris:Você está certo, todos os intervalos parecem ser múltiplos inteiros de algo em torno de 1/64 de segundo.Aliás, adicionar Thread.Sleep(...) no manipulador de eventos não faz nenhuma diferença.Isto faz sentido dado que System.Threading.Timer usa o pool de threads, então cada evento é disparado em um thread livre.

Foi útil?

Solução

Bem, na verdade estou obtendo um número diferente de até 100 Hz, com alguns grandes desvios, mas na maioria dos casos mais próximo do número solicitado (executando o XP SP3 com os SPs .NET mais recentes).

O System.Timer.Timer é implementado usando System.Threading.Timer, então isso explica por que você vê os mesmos resultados.Suponho que o temporizador seja implementado usando algum tipo de algoritmo de agendamento, etc.(é uma chamada interna, talvez olhar para o Rotor 2.0 possa esclarecer isso).

Eu sugeriria implementar um tipo de temporizador usando outro thread (ou uma combinação deles) chamando Sleep e um retorno de chamada.Não tenho certeza sobre o resultado.

Caso contrário, você pode dar uma olhada temporizadores multimídia (PInvoke).

Outras dicas

Se você usar winmm.dll, poderá usar mais tempo de CPU, mas terá melhor controle.

Aqui está o seu exemplo modificado para usar os temporizadores winmm.dll

const String WINMM = "winmm.dll";
const String KERNEL32 = "kernel32.dll";

delegate void MMTimerProc (UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2);

[DllImport(WINMM)]
static extern uint timeSetEvent(
      UInt32            uDelay,      
      UInt32            uResolution, 
      [MarshalAs(UnmanagedType.FunctionPtr)] MMTimerProc lpTimeProc,  
      UInt32            dwUser,      
      Int32             fuEvent      
    );

[DllImport(WINMM)]
static extern uint timeKillEvent(uint uTimerID);

// Library used for more accurate timing
[DllImport(KERNEL32)]
static extern bool QueryPerformanceCounter(out long PerformanceCount);
[DllImport(KERNEL32)]
static extern bool QueryPerformanceFrequency(out long Frequency);

static long CPUFrequency;

static int count;

static void Main(string[] args)
{            
    QueryPerformanceFrequency(out CPUFrequency);

    int[] frequencies = { 5, 10, 15, 20, 30, 50, 75, 100, 200, 500 };

    foreach (int freq in frequencies)
    {
        count = 0;

        long start = GetTimestamp();

        // start timer
        uint timerId = timeSetEvent((uint)(1000 / freq), 0, new MMTimerProc(TimerFunction), 0, 1);

        // wait 10 seconds
        while (DeltaMilliseconds(start, GetTimestamp()) < 10000)
        {
            Thread.Sleep(1);
        }

        // end timer
        timeKillEvent(timerId);

        Console.WriteLine("Requested frequency: {0}\nActual frequency: {1}\n", freq, count / 10);
    }

    Console.ReadLine();
}

static void TimerFunction(UInt32 timerid, UInt32 msg, IntPtr user, UInt32 dw1, UInt32 dw2)
{
    Interlocked.Increment(ref count);
}

static public long DeltaMilliseconds(long earlyTimestamp, long lateTimestamp)
{
    return (((lateTimestamp - earlyTimestamp) * 1000) / CPUFrequency);
}

static public long GetTimestamp()
{
    long result;
    QueryPerformanceCounter(out result);
    return result;
}

E aqui está a saída que recebo:

Requested frequency: 5
Actual frequency: 5

Requested frequency: 10
Actual frequency: 10

Requested frequency: 15
Actual frequency: 15

Requested frequency: 20
Actual frequency: 19

Requested frequency: 30
Actual frequency: 30

Requested frequency: 50
Actual frequency: 50

Requested frequency: 75
Actual frequency: 76

Requested frequency: 100
Actual frequency: 100

Requested frequency: 200
Actual frequency: 200

Requested frequency: 500
Actual frequency: 500

Espero que isto ajude.

Essas classes não se destinam ao uso em tempo real e estão sujeitas à natureza de agendamento dinâmico de um sistema operacional como o Windows.Se você precisar de execução em tempo real, provavelmente desejará examinar algum hardware incorporado.Não tenho 100% de certeza, mas acho que .netcpu pode ser uma versão em tempo real de um tempo de execução .NET menor em um chip.

http://www.arm.com/markets/emerging_applications/armpp/8070.html

Claro - você precisa avaliar a importância da precisão desses intervalos, visto que o código anexado a eles será executado em um sistema operacional que não seja em tempo real.A menos, claro, que esta seja uma questão puramente acadêmica (nesse caso - sim, é interessante!:P).

Parece que as frequências reais do seu temporizador são 63,9 Hz ou múltiplos inteiros delas.

Isso implicaria uma resolução do temporizador de cerca de 15 ms (ou múltiplos inteiros disso, ou seja,30 ms, 45 ms, etc.).

Isto, temporizadores baseados em múltiplos inteiros de um 'tick', é esperado (no DOS, por exemplo, o valor do 'tick' era 55 ms / 18 Hz).

Não sei por que sua contagem de ticks é de 15,65 mec em vez de 15 ms.Como experiência, e se você dormir por vários ms no manipulador do timer:podemos estar vendo 15 ms entre os tiques e 0,65 ms no manipulador do cronômetro a cada tique?

O Windows (e, portanto, o .NET rodando sobre ele) é um sistema operacional multitarefa preventivo.Qualquer thread pode ser interrompido a qualquer momento por outro thread, e se o thread de preempção não se comportar corretamente, você não recuperará o controle quando quiser ou precisar isto.

Em poucas palavras, é por isso que você não pode ter certeza de obter horários exatos e por que o Windows e o .NET são plataformas inadequadas para certos tipos de software.Se vidas estão em perigo porque você não consegue o controle EXATAMENTE quando deseja, escolha uma plataforma diferente.

Se você precisar mudar para um ambiente em tempo real, usei RTX no passado quando era necessária amostragem determinística (de um dispositivo serial personalizado) e tive muita sorte com isso.

http://www.pharlap.com/rtx.htm

Aqui está uma boa implementação de um temporizador usando um temporizador multimídiahttp://www.softwareinteractions.com/blog/2009/12/7/using-the-multimedia-timer-from-c.html

Há vários motivos pelos quais o temporizador está desligado.Exemplo com o hardware, o mesmo thread está ocupado com outro processamento e assim por diante...

Se você quiser um tempo mais preciso, use a classe Stopwatch no namespace System.Diagnostics.

Parte do problema é que os temporizadores têm dois atrasos para contabilizar.Isso pode variar dependendo de como o temporizador é implementado no sistema operacional.

  1. O tempo que você solicita para esperar
  2. O tempo entre a ocorrência do número 1 e a chegada do processo na fila de agendamento.

O cronômetro tem bom controle do número 1, mas quase nenhum controle do número 2.Ele pode sinalizar ao sistema operacional que gostaria de ser executado novamente, mas o sistema operacional está livre para ativá-lo quando desejar.

Com base em seus comentários, você não deveria usar um cronômetro.Você deveria usar um loop com cronômetro para verificar o intervalo e um spinlock para não perder o quantum.

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