Scelta tra i tre timer di .NET - PeriodicTimer / Timer / DispatcherTimer
· Aggiornato il: · Go Komura · 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
Dispatcherdel thread dell’UI
Ciò che tende a confondersi nella pratica è più o meno questo.
- Passaggio di una lambda
asyncaSystem.Threading.Timeranche 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
DispatcherTimere 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
- Prima la conclusione (in una riga)
- La panoramica in un foglio
- 2.1. Il quadro generale
- 2.2. La tabella decisionale di primo passaggio
- 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
- 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
- 4.1. Per lavoro periodico asincrono:
- Anti-pattern comuni
- Lista di controllo per la revisione del codice
- Una guida decisionale approssimativa
- Riepilogo
- Riferimenti
1. Prima la conclusione (in una riga)
- Se vuoi scrivere un lavoro a intervalli fissi in modo naturale su base
await, inizia conPeriodicTimer - 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.Timerpossono sovrapporsi. Riempire in modo sciatto il lavoro asincrono diventa disordinato velocemente DispatcherTimerti 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:
- Su quale thread / contesto vuoi che venga eseguito?
- Vuoi scrivere il corpo in sequenza con
async/await? - 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
flowchart LR
A["Vuoi fare qualcosa a un intervallo fisso"] --> B{"Lo vuoi nel thread dell'UI?"}
B -- "SÌ" --> C["DispatcherTimer"]
B -- "NO" --> D{"Vuoi scrivere body<br/>chiaramente con<br/>async / await?"}
D -- "SÌ" --> E["PeriodicTimer"]
D -- "NO" --> F{"Vuoi attivare light<br/>callback sul<br/>ThreadPool?"}
F -- "SÌ" --> G["System.Threading.Timer"]
F -- "NO" --> H["Considera altri design<br/>Channel / 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.TimereDispatcherTimersono in stile callback / eventoPeriodicTimerè lo stile in cuiawaitil 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
DispatcherTimerrende 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.
- Usalo una tantum e un solo consumatore
- 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
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
awaitche 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.Timercome variabile locale e non contenere un riferimento - Lasciare vaga la storia di
Dispose()senza fermare la storia diSystem.Threading.Timer - Non chiamare mai
Stop()su unDispatcherTimer, 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
DispatcherTimercontiene 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
DispatcherTimerquando 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
awaite 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 asincronoSystem.Threading.Timerè il timer per i callback ThreadPoolDispatcherTimerè il timer per l’UI
Con questo mnemonico, raramente sbagli di molto.
8. Riepilogo
Ciò che conta davvero nella scelta di un timer .NET non è la differenza nei nomi, ma questi tre punti.
- Dove corre
- In quale flusso vuoi scriverlo
- Come gestisci sovrapposizioni e ritardi
Come politica, questo da solo è sufficiente per farcela.
- Lavoro periodico asincrono:
PeriodicTimer - Richiami leggeri su ThreadPool:
System.Threading.Timer - WPF Aggiornamenti dell’UI:
DispatcherTimer - 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 asincronoSystem.Threading.Timerè uno strumento per eseguire periodicamente il kick dei callbackDispatcherTimerè 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
- Codice di esempio completo per questo articolo (libreria, demo, test unitari) https://github.com/gomurin0428/komurasoft-blog-samples/tree/main/periodictimer-system-threading-timer-dispatchertimer-guide
- Articolo correlato: Una guida pratica per ottenere il massimo Soft Real-Time nell’Windows ordinario: la lista di controllo da considerare per prima
- Articolo correlato: C# Best practice asincrone / in attesa: la tabella decisionale da esaminare per prima
- Articolo correlato: WPF / WinForms asincrono e thread dell’UI su un foglio: Dove attendono i resi, Dispatcher, ConfigureAwait e Dove .Result / .Wait() rimangono bloccati
- Temporizzatori - .NET
- PeriodicTimer Classe
- PeriodicTimer.WaitForNextTickAsync(CancellationToken) Metodo
- PeriodicTimer.Dispose Metodo
- PeriodicTimer Costruttore
- Classe timer (System.Threading)
- Costruttore timer (System.Threading)
- Attività in background con servizi ospitati in ASP.NET Core
- DispatcherTimer Classe (System.Windows.Threading)
- DispatcherTimer Classe (Microsoft.UI.Xaml)
Articoli correlati
Articoli recenti con gli stessi tag per approfondire argomenti vicini.
Perché utilizzare .NET Generic Host e BackgroundService nelle app desktop
Come utilizzare Generic Host e BackgroundService per organizzare l'avvio, l'elaborazione periodica, l'arresto, la registrazione, la confi...
Icone nella system tray e notifiche toast nelle app Windows — le insidie di NotifyIcon e come scegliere l'AppNotification giusta
Una guida pratica per mantenere un'applicazione Windows aziendale residente nella system tray (area di notifica) e avvisare l'utente tram...
Qual è il .NET Generic Host? - La base per DI, configurazione e registrazione
Il .NET Generic Host in parole povere: un posto per DI, configurazione, registrazione, BackgroundService e arresto regolare. Esempio di c...
WPF / WinForms asincrono e il thread dell'UI su un foglio
Dopo l'attesa in WPF o WinForms, quale thread esegue il tuo codice? Copre Dispatcher.Invoke, ConfigureAwait(false) e perché .Result e .Wa...
Una guida pratica a FileSystemWatcher: gestire gli eventi persi e duplicati
Organizziamo come utilizzare FileSystemWatcher e le sue insidie: eventi persi, notifiche duplicate, trappole di rilevamento del completam...
Argomenti correlati
Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.
Argomenti tecnici Windows
Portale su sviluppo Windows, analisi dei problemi e valorizzazione delle risorse esistenti.
Thread UI e timer
Thread UI WPF / WinForms, flussi asincroni, Dispatcher e progettazione dei timer.
Servizi collegati all’argomento
L’articolo è direttamente collegato ai servizi seguenti.
Sviluppo di applicazioni Windows
Nello sviluppo di applicazioni Windows che prevede esecuzione periodica, aggiornamenti dell'UI ed elaborazione in background, la selezione del timer influisce direttamente sulla qualità dell'implementazione.
Consulenza tecnica e revisione del progetto
Se sei nella fase di decidere dove dividere le responsabilità di PeriodicTimer e DispatcherTimer, questo può essere considerato come un impegno di consulenza tecnica e revisione del progetto.
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.