Scelta tra i tre timer di .NET - PeriodicTimer / Timer / DispatcherTimer

· Aggiornato il: · · C#, .NET, WPF, Timer, Progettazione

Nell’articolo precedente, Una guida pratica per ottenere il massimo Soft Real-Time nell’Windows ordinario - La lista di controllo da considerare per prima cosa, abbiamo trattato come evitare i cicli periodici guidati da Sleep a favore di approcci guidati dagli eventi e timer waitable.

Quindi cosa fai nello sviluppo quotidiano di app .NET? Il luogo in cui le persone si perdono sono PeriodicTimer, System.Threading.Timer e DispatcherTimer.

Si chiamano tutti timer, ma i loro caratteri differiscono notevolmente:

  • Un timer il cui ticchettio await
  • Un timer le cui richiamate arrivano sul ThreadPool
  • Un timer che viene eseguito sul Dispatcher del thread dell’UI

Ciò che tende a confondersi nella pratica è più o meno questo.

  • Passaggio di una lambda async a System.Threading.Timer anche se il lavoro periodico è asincrono
  • Toccando lo schermo direttamente da un timer ThreadPool anche se si tratta di un aggiornamento dell’UI WPF
  • Mettere un lavoro pesante in un DispatcherTimer e rallentare l’intero schermo
  • La discussione precedente su “soft real-time” e l’esecuzione periodica dell’app ordinaria si confondono nella tua testa

Questo articolo presuppone principalmente app generali C# / .NET su .NET 6 o versioni successive e organizza PeriodicTimer / System.Threading.Timer / DispatcherTimer in un ordine che rende la pratica quotidiana meno confusa.

Gli obiettivi previsti sono questi.

  • Lavoratori / servizi di fondo
  • App della console
  • Elaborazione dietro le quinte in ASP.NET Core
  • Applicazioni desktop WPF

Con DispatcherTimer, questo articolo significa principalmente System.Windows.Threading.DispatcherTimer di WPF. WinUI / UWP hanno un DispatcherTimer con la stessa idea. Per WinForms, è più naturale considerare System.Windows.Forms.Timer come timer dell’UI.

Tieni presente che ciò di cui ci occupiamo qui è come scrivere l’esecuzione periodica sul lato dell’app. Quando l’argomento è l’accuratezza del periodo stesso, torniamo all’articolo soft real-time.

Inoltre, il codice visualizzato in questo articolo è pubblicato su GitHub come set di esempi completo compilabile ed eseguibile (librerie e demo della console per PeriodicTimer / System.Threading.Timer, oltre a test unitari che verificano la coalescenza dei tick e la sovrapposizione dei callback).

periodictimer-system-threading-timer-dispatchertimer-guida - komurasoft-blog-samples (GitHub)

Sommario

  1. Prima la conclusione (in una riga)
  2. La panoramica in un foglio
    • 2.1. Il quadro generale
    • 2.2. La tabella decisionale di primo passaggio
  3. Cosa distinguere prima
    • 3.1. Stile di richiamata o tick di attesa?
    • 3.2. Funziona su ThreadPool o sul thread dell’UI?
    • 3.3. Le lavorazioni periodiche e le garanzie di precisione sono argomenti distinti
  4. Modelli tipici
    • 4.1. Per lavoro periodico asincrono: PeriodicTimer
    • 4.2. Per richiamate leggere su ThreadPool: System.Threading.Timer
    • 4.3. Per gli aggiornamenti dell’UI WPF: DispatcherTimer
    • 4.4. Per il lavoro periodico in tempo reale: guarda altri strumenti
  5. Anti-pattern comuni
  6. Lista di controllo per la revisione del codice
  7. Una guida decisionale approssimativa
  8. Riepilogo
  9. Riferimenti

1. Prima la conclusione (in una riga)

  • Se vuoi scrivere un lavoro a intervalli fissi in modo naturale su base await, inizia con PeriodicTimer
  • Se desideri attivare periodicamente richiamate leggere sul ThreadPool, utilizza System.Threading.Timer
  • Se desideri aggiornare lo schermo sul thread dell’UI di WPF, utilizza DispatcherTimer
  • Le richiamate System.Threading.Timer possono sovrapporsi. Riempire in modo sciatto il lavoro asincrono diventa disordinato velocemente
  • DispatcherTimer ti consente di toccare direttamente l’UI, ma in cambio, il lavoro pesante blocca facilmente l’UI stessa
  • Nel contesto soft real-time dell’articolo precedente, questi tre non sono i protagonisti dell’attesa ad alta precisione

In breve, le prime tre cose da guardare sono:

  1. Su quale thread / contesto vuoi che venga eseguito?
  2. Vuoi scrivere il corpo in sequenza con async / await?
  3. Riesci a tollerare callback sovrapposti?

La semplice separazione di questi tre rende le cose molto meno confuse.

2. Panoramica in un unico foglio

2.1. Il quadro generale

NONONOVuoi fare qualcosa a un intervallo fissoLo vuoi nel thread dell'UI?DispatcherTimerVuoi scrivere bodychiaramente conasync / await?PeriodicTimerVuoi attivare lightcallback sulThreadPool?System.Threading.TimerConsidera altri designChannel / BackgroundService / evento / timer di attesa

In pratica, questa ramificazione è per lo più sufficiente. In caso di dubbio, il taglio meno soggetto a errori da eseguire per primo è: PeriodicTimer per il lavoro asincrono, DispatcherTimer per gli aggiornamenti dell’UI.

System.Threading.Timer è utile, ma con la sovrapposizione delle richiamate e le peculiarità della gestione della durata, è un po’ capriccioso come prima scelta.

2.2. La tabella decisionale di primo passaggio

Situazione Prima scelta Dove corre Perché si adatta Prima avvertenza
Esegui operazioni asincrone come HTTP / DB / I / O file a intervalli fissi PeriodicTimer All’interno del flusso del tuo attuale metodo asincrono Scrivibile su base await; l’interruzione e la cancellazione sono naturali Un timer, un consumatore. I ritardi non vengono parallelizzati automaticamente
Esegui un controllo leggero del battito cardiaco / emissione di metriche / scadenza della cache su ThreadPool System.Threading.Timer ThreadPool Leggero, in stile callback. Facile da inserire nei progetti esistenti basati su callback I callback presuppongono il rientro. Possono sovrapporsi. Tenere un riferimento
Esegui la visualizzazione dell’orologio WPF o aggiornamenti leggeri dell’UI a intervalli fissi DispatcherTimer Dispatcher di WPF (thread dell’UI) Può toccare direttamente l’UI. Ha priorità I tempi di cottura esatti non sono garantiti. Il lavoro pesante intasa l’UI
La precisione del periodo è il punto e vuoi evitare cicli guidati da Sleep Non rendere questi tre i protagonisti - L’obiettivo non è l’esecuzione periodica a livello di app, ma la progettazione della precisione dell’attesa Guarda invece eventi / timer di attesa

Ciò che conta in questa tabella è guardare il luogo di esecuzione e lo stile di scrittura, non il nome del timer. Quando la selezione del timer va male, la causa più spesso non è il “dove viene eseguito” piuttosto che il nome dell’API.

3. Cosa distinguere prima

3.1. Stile di richiamata o tick di attesa?

Separarlo migliora immediatamente la visibilità.

  • System.Threading.Timer e DispatcherTimer sono in stile callback / evento
  • PeriodicTimer è lo stile in cui await il segno di spunta successivo

Cioè:

  • Stile di richiamata: “il timer ti chiama”
  • PeriodicTimer: “aspetti il prossimo tick”

Se il corpo è async e vuoi leggere “aspetta → processo → aspetta di nuovo” come un flusso continuo, PeriodicTimer è più naturale.

Viceversa, quando:

  • Desideri inserirti in un progetto esistente basato su callback
  • Il corpo è corto e sincrono
  • Vuoi semplicemente un calcio periodico

allora System.Threading.Timer si adatta.

PeriodicTimer è conveniente ma non universale. Non è progettato per più chiamate simultanee WaitForNextTickAsync su un timer, e se si verificano più segni di spunta mentre non stai aspettando, vengono riuniti in uno solo.

È importante non fraintenderlo poiché “si aggiorna automaticamente”.

3.2. Funziona su ThreadPool o sul thread dell’UI?

La prossima cosa da considerare è dove viene eseguito.

I callback System.Threading.Timer vengono eseguiti su ThreadPool, non sul thread di creazione. Ciò lo rende adatto per il lavoro in background, ma non è pensato per toccare direttamente l’UI.

DispatcherTimer, al contrario, è un timer dell’UI integrato nella coda Dispatcher. In WPF viene eseguito sullo stesso Dispatcher, quindi puoi aggiornare l’UI direttamente all’interno del gestore Tick.

Questa differenza è abbastanza grande.

  • Per toccare l’UI da un timer ThreadPool, è necessario eseguire esplicitamente il reindirizzamento all’UI
  • DispatcherTimer rende l’UI facile da toccare, ma di conseguenza consuma tempo nel thread dell’UI

In altre parole, il punto di forza di DispatcherTimer è “l’accesso sicuro all’UI”, ma ciò significa allo stesso tempo che “il lavoro pesante trascina anche l’input e il rendering”.

3.3. Le lavorazioni periodiche e le garanzie di precisione sono argomenti distinti

Questa parte conta come collegamento all’articolo precedente.

Anche se la frase “fai qualcosa a un intervallo fisso” è la stessa,

  • Desiderio di lavoro periodico ogni pochi secondi come comodità a livello di app
  • Volere avvicinarsi il più possibile a una scadenza su una scala da 1 ms a pochi ms

sono problemi diversi.

System.Threading.Timer è un timer leggero e maneggevole, ma non è uno strumento dedicato alla precisione. Anche DispatcherTimer è influenzato dallo stato della coda Dispatcher e dalle priorità.

PeriodicTimer può sembrare, già dal nome, come “il periodo deve essere stretto,” ma la sua forza pratica è la scrivibilità del flusso asincrono, non la precisione.

Quindi è più sicuro separare fin dall’inizio se si desidera:

  • Scrivi esecuzione periodica a livello di app o
  • Stringi attendi precisione

Quando questi due si fondono, la discussione sulla selezione del timer si sposta gradualmente verso qualcosa di strano.

4. Modelli tipici

4.1. Per lavoro periodico asincrono: PeriodicTimer

In un processo di lavoro, BackgroundService o console residente, se desideri eseguire un lavoro asincrono a intervalli fissi, PeriodicTimer è il metodo più semplice con cui scrivere.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

public sealed class CacheRefreshWorker : BackgroundService
{
    private readonly ILogger<CacheRefreshWorker> _logger;

    public CacheRefreshWorker(ILogger<CacheRefreshWorker> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("CacheRefreshWorker started.");

        await RefreshCacheAsync(stoppingToken);

        using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
        try
        {
            while (await timer.WaitForNextTickAsync(stoppingToken))
            {
                await RefreshCacheAsync(stoppingToken);
            }
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("CacheRefreshWorker stopping.");
        }
    }

    private async Task RefreshCacheAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Refreshing cache...");
        await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
    }
}

Le virtù di questa forma:

  • Il flusso del codice è facile da seguire come un unico metodo async
  • Il CancellationToken è facile da passare direttamente a valle
  • Meno gestione della durata basata su callback e gestione delle eccezioni

È particolarmente adatto quando il corpo è incentrato sull’attesa I / O:

  • Chiamando HTTP
  • Interrogazione di un DB
  • Lettura di file
  • In attesa di altro asincrono API

Ci sono due precauzioni.

  1. Usalo una tantum e un solo consumatore
  2. Decidi tu quale sarà la politica quando l’elaborazione richiede più tempo del periodo

PeriodicTimer non si parallelizza automaticamente per recuperare il ritardo solo perché l’iterazione precedente è durata a lungo. In questo senso, è un timer per “scrivere in modo naturale un ciclo asincrono a intervallo fisso”.

Se ti interessa anche la testabilità, anche l’overload del costruttore che accetta un TimeProvider è abbastanza conveniente.

4.2. Per richiamate leggere su ThreadPool: System.Threading.Timer

Se tutto ciò che desideri è invocare periodicamente una breve richiamata, System.Threading.Timer è semplice.

Ad esempio:

  • Emettere un battito cardiaco
  • Raccolta delle metriche della luce
  • Inserimento di un assegno di scadenza a breve
  • Sospensione di un progetto esistente basato su callback ```csharp using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging;

public sealed class HeartbeatService : IHostedService, IDisposable { private readonly ILogger _logger; private Timer? _timer; private int _running;

public HeartbeatService(ILogger<HeartbeatService> logger)
{
    _logger = logger;
}

public Task StartAsync(CancellationToken cancellationToken)
{
    _timer = new Timer(OnTimer, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
    return Task.CompletedTask;
}

private void OnTimer(object? state)
{
    if (Interlocked.Exchange(ref _running, 1) != 0)
    {
        return;
    }

    try
    {
        _logger.LogInformation("Heartbeat: {Now}", DateTimeOffset.Now);
    }
    finally
    {
        Volatile.Write(ref _running, 0);
    }
}

public Task StopAsync(CancellationToken cancellationToken)
{
    _timer?.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
    return Task.CompletedTask;
}

public void Dispose()
{
    _timer?.Dispose();
} } ``` Il motivo per cui questo esempio include `Interlocked.Exchange` è questo `System.Threading.Timer` **non attende il completamento della richiamata precedente**.

Questa parte è piuttosto importante.

  • Le richiamate vengono eseguite su ThreadPool
  • Le richiamate sono rientranti per presupposto
  • Se il lavoro dura più a lungo dell’intervallo, le richiamate possono sovrapporsi

Se il lavoro non è leggero è più tranquillo progettare con:

  • Saltare le invocazioni duplicate
  • Spingersi in coda
  • Trasferimento a PeriodicTimer

Un’altra cosa abbastanza importante: mantenere una referenza. Un System.Threading.Timer diventa idoneo per GC quando non rimane alcun riferimento, anche se attivo. Inoltre, anche subito dopo aver chiamato Dispose(), le richiamate già in coda potrebbero comunque essere eseguite in seguito.

Quindi System.Threading.Timer è:

  • Leggero
  • Veloce
  • Semplice

  • ma in cambio, è un timer in cui devi assumerti adeguatamente le circostanze della richiamata.

4.3. Per gli aggiornamenti dell’UI WPF: DispatcherTimer

Se desideri aggiornare periodicamente l’orologio sullo schermo o la visualizzazione dello stato della luce in WPF, DispatcherTimer è naturale.

using System;
using System.Windows;
using System.Windows.Threading;

public partial class MainWindow : Window
{
    private readonly DispatcherTimer _clockTimer;

    public MainWindow()
    {
        InitializeComponent();

        _clockTimer = new DispatcherTimer(DispatcherPriority.Background)
        {
            Interval = TimeSpan.FromSeconds(1)
        };
        _clockTimer.Tick += ClockTimer_Tick;
        _clockTimer.Start();
    }

    private void ClockTimer_Tick(object? sender, EventArgs e)
    {
        ClockText.Text = DateTime.Now.ToString("HH:mm:ss");
    }

    protected override void OnClosed(EventArgs e)
    {
        _clockTimer.Stop();
        _clockTimer.Tick -= ClockTimer_Tick;
        base.OnClosed(e);
    }
}

Il vantaggio di DispatcherTimer è che Tick viene elaborato su Dispatcher di WPF, quindi puoi toccare direttamente l’UI.

Questo si abbina bene, ad esempio, a:

  • Visualizza l’orologio
  • Aggiornamento luminoso degli indicatori di stato della connessione
  • Trigger per la rivalutazione dei comandi
  • Aggiornamenti leggeri dei numeri mostrati sullo schermo

Ma anche qui c’è un punto in cui l’umore cambia.

DispatcherTimer gira sul thread dell’UI, quindi il lavoro pesante nel gestore Tick trascina con sé input, rendering e layout.

Inoltre, DispatcherTimer non è uno strumento che garantisce “esattamente all’ora specificata”. È influenzato da altro lavoro sulla coda Dispatcher e dalle priorità.

Quindi, in pratica, le cose rimangono stabili se tieni presente:

  • Mantieni il corpo di Tick leggero
  • Scaricare altrove il pesante lavoro di I / O e CPU
  • Alla chiusura, chiama Stop() e annulla l’iscrizione, rendendo esplicita la durata

4.4. Per il lavoro periodico in tempo reale: guarda altri strumenti

Questo è il punto di connessione con l’articolo precedente.

Ciò di cui trattava l’articolo soft real-time non lo era “correre all’incirca ogni tanti secondi va bene”, ma come ridurre il jitter nei periodi e il mancato rispetto delle scadenze.

In questo contesto i temi sono:

  • Non fare affidamento sulle attese relative basate su Sleep
  • Utilizzare approcci guidati dagli eventi e timer di attesa
  • Dividere il percorso veloce e il percorso lento
  • Misurare il ritardo

Quindi è più pulito dividere il problema dall’inizio:

  • Lavoro periodico asincrono quotidiano in un’app → PeriodicTimer
  • ThreadPool richiamate → System.Threading.Timer
  • Aggiornamenti dell’UI → DispatcherTimer
  • La precisione del periodo stesso è la protagonista → il mondo dell’articolo precedente

La domanda “Voglio correre il più strettamente possibile ogni 1 ms: quale timer .NET è il migliore?” è, circa a metà strada, non più una questione di selezione del timer ma una questione di metodi di attesa e progettazione.

5. Anti-pattern comuni

5.1. Passaggio di un async Lambda direttamente a System.Threading.Timer

Questo è molto allettante.

_timer = new Timer(async _ => await RefreshAsync(), null,
    TimeSpan.Zero, TimeSpan.FromSeconds(5));

Sembra ordinato, ma TimerCallback è void. Quindi questo async lambda viene effettivamente trattato come async void.

Di conseguenza:

  • Il chiamante non può aspettarlo
  • Non è possibile attendere il completamento
  • La gestione delle eccezioni diventa difficile
  • È inoltre necessario considerare la sovrapposizione della richiamata

– una situazione piuttosto paludosa.

La gestione delle eccezioni è difficile per un motivo che merita di essere chiarito. Se fosse async Task, l’eccezione salirebbe sul Task e il chiamante la riceverebbe quando awaita. Con async void (o equivalente) non c’è quel Task: l’eccezione viene rilanciata direttamente al SynchronizationContext attivo quando il metodo è iniziato. Il callback di System.Threading.Timer gira su un thread del ThreadPool, dove non c’è SynchronizationContext. Di conseguenza, l’eccezione diventa un’eccezione non gestita sul thread del ThreadPool e, per impostazione predefinita, fa terminare l’intero processo. A meno di non racchiudere il corpo del callback in try / catch manualmente, non c’è modo di contenerla dall’esterno.

Se il corpo è asincrono, è meglio considerare prima PeriodicTimer.

5.2. Mettere il lavoro pesante in un segno di spunta DispatcherTimer

DispatcherTimer può toccare direttamente l’UI, quindi la tentazione è di scrivere tutto lì. Ma questo è il thread dell’UI.

Inserisci:

  • Lavoro sincrono lungo
  • Calcolo pesante della CPU
  • Blocco I / O
  • Opera contenente lunghi await che possono fare doppio fuoco

e ti scontri frontalmente con l’input e il rendering dell’UI.

Mantieni il corpo del gestore Tick leggero, scarica il lavoro pesante in background e riporta solo i risultati necessari nell’UI: è più stabile.

5.3. Supponendo che PeriodicTimer compensi automaticamente i ritardi

Anche questo è un facile malinteso.

PeriodicTimer è eccellente come strumento per scrivere in modo pulito cicli asincroni a intervalli fissi, ma non esegue da solo le iterazioni in parallelo per recuperare il ritardo quando la precedente dura a lungo.

Poiché i tick che si verificano mentre non stai aspettando possono essere riuniti in uno solo, devi decidere in base alla progettazione:

  • Salti quando sei in ritardo?
  • È rilevante solo l’ultimo stato?
  • Oppure è necessario elaborare ogni singola iterazione?

5.4. Posticipo dello stop e gestione della vita

I timer causano più incidenti quando si fermano che quando corrono.

Gli oggetti facili da perdere sono questi.

  • Creare un System.Threading.Timer come variabile locale e non contenere un riferimento
  • Lasciare vaga la storia di Dispose() senza fermare la storia di System.Threading.Timer
  • Non chiamare mai Stop() su un DispatcherTimer, non annullare mai l’iscrizione a Tick
  • Il timer estende la durata degli oggetti anche dopo la chiusura dello schermo

DispatcherTimer in particolare può mantenere vivo l’oggetto a cui è legato il suo gestore. Se provi quella strana sensazione di “eh, questa finestra dovrebbe essere chiusa ma è ancora in giro”, ecco dove cercare.

6. Lista di controllo per la revisione del codice

  • Puoi spiegare se il lavoro periodico deve essere scritto come aggiornamento dell’UI / richiamata ThreadPool / loop asincrono?
  • Un corpo asincrono viene forzato in un timer in stile callback?
  • Se si utilizza System.Threading.Timer, il codice può tollerare callback sovrapposti o è protetto?
  • Il segno di spunta DispatcherTimer contiene lavoro pesante, blocco di I / O o elaborazione sincrona lunga?
  • Se si utilizza PeriodicTimer, è stata decisa la politica per il ritardo?
  • Il metodo di arresto (Change / Dispose / Stop) e il flusso di arresto dell’app sono chiari?
  • Il riferimento al System.Threading.Timer è corretto?
  • C’è la cancellazione dell’iscrizione e la pulizia per DispatcherTimer quando lo schermo si chiude?
  • Il problema è stato suddiviso, all’inizio, in “esecuzione periodica a livello di app” e “precisione di attesa”?

7. Una guida decisionale approssimativa

Ecco le regole pratiche da seguire.

  • Premi un’API ogni 30 secondi per aggiornare le impostazioni → PeriodicTimer

  • Invia un battito cardiaco o metriche di luce ogni 5 secondi → System.Threading.Timer

  • Mostra un orologio o aggiornamenti sullo stato della luce in WPF → DispatcherTimer

  • Tocca l’UI direttamente su ogni segno di spunta → DispatcherTimer

  • Il corpo del lavoro periodico è pieno di await e desideri che le interruzioni e le eccezioni siano gestite in modo naturale → PeriodicTimer

  • Aggiungi un piccolo calcio basato su richiamata a basso costo → System.Threading.Timer

  • Il punto è gestire la precisione del periodo e il jitter su una scala di 1-5 ms → prima di questi tre, guarda i metodi di attesa nell’articolo precedente

Mettilo molto senza mezzi termini in una riga ciascuno:

  • PeriodicTimer è il timer per asincrono
  • System.Threading.Timer è il timer per i callback ThreadPool
  • DispatcherTimer è il timer per l’UI

Con questo mnemonico, raramente sbagli di molto.

Ciò che conta davvero nella scelta di un timer .NET non è la differenza nei nomi, ma questi tre punti.

  1. Dove corre
  2. In quale flusso vuoi scriverlo
  3. Come gestisci sovrapposizioni e ritardi

Come politica, questo da solo è sufficiente per farcela.

  1. Lavoro periodico asincrono: PeriodicTimer
  2. Richiami leggeri su ThreadPool: System.Threading.Timer
  3. WPF Aggiornamenti dell’UI: DispatcherTimer
  4. La precisione protagonista: guarda gli altri metodi di attesa

I timer si confondono perché i nomi sono simili. Ma i loro ruoli non sono così simili.

  • PeriodicTimer è uno strumento per modellare il flusso asincrono
  • System.Threading.Timer è uno strumento per eseguire periodicamente il kick dei callback
  • DispatcherTimer è uno strumento per aggiornamenti periodici sul thread dell’UI

Il solo fatto di pensare a questi tre separatamente rende il codice notevolmente più silenzioso.

Viceversa, quando si fondono:

  • Ciò che dovrebbe essere asincrono diventa async void-ish
  • Il codice si blocca toccando direttamente l’UI
  • Le richiamate si sovrappongono e lo stato diventa confuso
  • Viene inclusa anche la discussione sulla precisione del periodo

  • ne conseguono problemi abbastanza comuni.

Inizia guardando “dove vuoi che venga eseguito”. Questo da solo rende la selezione del timer molto più tranquilla.

9. Riferimenti

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

L’articolo è direttamente collegato ai servizi seguenti.

Domande frequenti

Domande che ricorrono nelle consulenze sull’argomento dell’articolo.

Quale timer .NET devo utilizzare per il lavoro asincrono periodico?
Inizia con PeriodicTimer. Ti consente di scrivere un ciclo a intervallo fisso in modo naturale in base all'attesa, con arresto e cancellazione che passano attraverso un CancellationToken, e si adatta particolarmente bene quando il corpo è un lavoro incentrato sull'I / O come chiamate HTTP, query di database o letture di file. Usalo su base one-time, one-consumer e tieni presente che non esegue automaticamente la parallelizzazione per recuperare il ritardo quando un'iterazione viene eseguita più a lungo del periodo. Per la testabilità, esiste anche un sovraccarico del costruttore che accetta un TimeProvider.
I callback System.Threading.Timer possono sovrapporsi?
SÌ. System.Threading.Timer non attende il completamento della richiamata precedente, quindi se il lavoro dura più a lungo dell'intervallo, le richiamate possono sovrapporsi su ThreadPool. Se il lavoro non è banalmente leggero, guardati dal rientro (ad esempio con Interlocked.Exchange), sposta il lavoro in una coda o spostalo su PeriodicTimer. Mantieni anche un riferimento al timer, perché diventa idoneo per la Garbage Collection quando non rimane alcun riferimento, anche se attivo, e i callback già in coda possono ancora essere eseguiti dopo la chiamata a Dispose().
DispatcherTimer è accurato e posso svolgere un lavoro pesante nel suo gestore Tick?
DispatcherTimer non garantisce tempi di cottura esatti; è influenzato da altro lavoro nella coda del Dispatcher e dalle priorità. Il suo vero punto di forza è che Tick funziona sul thread dell'UI di WPF, quindi puoi aggiornare direttamente lo schermo. Quella stessa proprietà significa che il lavoro pesante nel gestore Tick trascina verso il basso input, rendering e layout, quindi mantieni il corpo Tick leggero, scarica il lavoro pesante di I / O o CPU in background e chiama Stop() e annulla l'iscrizione quando la finestra si chiude in modo che il timer non mantenga attiva la finestra.
PeriodicTimer compensa automaticamente i tick persi?
No. Se si verificano più tick mentre non sei in attesa di WaitForNextTickAsync, vengono riuniti in un singolo tick e il timer non esegue iterazioni in parallelo per recuperare il ritardo quando quello precedente dura a lungo. Devi decidere tu stesso la policy in base alla progettazione: salta quando è in ritardo, elabora solo lo stato più recente o assicurati che ogni iterazione venga gestita tramite qualche altro meccanismo come una coda.

Profilo dell’autore

Pagina di presentazione dell’autore dell’articolo.

Go Komura

Rappresentante di KomuraSoft LLC

Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.

Torna al blog