La Cache di ASP.NET è un tipico esempio di risorsa condivisa, a cui accedono contemporaneamente diversi thread. Un tale scenario è potenzialmente a rischio di race condition: quando due thread agiscono nello stesso istante sul medesimo oggetto, in generale il risultato finale non è determinabile a priori e questo, in assenza di meccanismi di sincronizzazione, può portare a bug e malfunzionamenti che sono difficili da individuare.
Se leggiamo la documentazione di MSDN relativa all'oggetto System.Web.Caching.Cache, possiamo notare che fortunatamente si tratta di un oggetto Thread Safe, ossia che possiede già internamente tutti i meccanismi per gestire correttamente accessi concorrenti. Tuttavia ciò non ci mette al riparo da utilizzi errati che, come sviluppatori, potremmo compiere interagendo con esso.
Consideriamo ad esempio questo frammento di codice, piuttosto comune quando si vuole determinare se aggiungere o meno un oggetto in cache:
var myResult = Cache["MyResult"] as SomeType; // (1) if (myResult == null) { myResult = ExecuteSomeQueryOnDatabase(); this.Cache["MyResult"] = myResult; // (2) }
Lo snippet precedente sembra formalmente corretto, eppure in un ambiente multithreading, come tipicamente è una Web Application, nulla vieta che, tra l'istante in cui viene recuperato il contenuto della cache (1) e quello in cui tale valore viene effettivamente scritto (2), un altro thread abbia eseguito il medesimo codice e già popolato la cache con il dato MyResult.
Nel migliore dei casi, l'effetto è l'esecuzione di una seconda query sul database (che comunque potrebbe essere evitata), ma in casi più complessi una tale situzione potrebbe comportare bug difficilmente diagnosticabili e riproducibili.
Per evitare queste eventualità, è sufficiente sincronizzare l'accesso in scrittura alla cache utilizzando una risorsa statica:
private static object syncObject = new object(); public SomeType GetSomeResult() { var myResult = Cache["MyResult"] as SomeType; if (myResult == null) { lock (syncObject) { if (Cache["MyResult"] == null) { myResult = ExecuteSomeQueryOnDatabase(); this.Cache["MyResult"] = myResult; } } } return myResult; }
Lo statement lock, infatti, assicura che solo un thread per volta possa acquisire la risorsa syncObject e quindi eseguire il codice all'interno del blocco. La duplice condizione di if (chiamata double check pattern) serve ad evitare di recuperare il dato dal database nel caso in cui un thread abbia scritto sulla cache mentre l'altro era in attesa di poter acquisire il lock.
Per quanto riguarda l'accesso in lettura, invece, non è necessaria alcuna precauzione particolare, grazie alla thread safety dell'oggetto Cache.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Bloccare l'esecuzione di un pod in mancanza di un'artifact attestation di GitHub
Rinnovare il token di una GitHub App durante l'esecuzione di un workflow
Gestione dei nomi con le regole @layer in CSS
Migliorare la sicurezza dei prompt con Azure AI Studio
Ordine e importanza per @layer in CSS
Creare una libreria CSS universale: Immagini
Inference di dati strutturati da testo con Semantic Kernel e ASP.NET Core Web API
Generare la software bill of material (SBOM) in GitHub
Gestire la cancellazione di una richiesta in streaming da Blazor
Garantire la provenienza e l'integrità degli artefatti prodotti su GitHub
Utilizzare il trigger SQL con le Azure Function
Utilizzare WhenEach per processare i risultati di una lista di task
I più letti di oggi
- Validazione automatica dei parametri in Web API con ASP.NET Core 2.1
- Recuperare i file utilizzati di recente in un'Universal App
- Applicare un'animazione al contenuto di un ContentControl nella Universal Windows Platform
- AI&ML Conference 2019 - Milano
- .NET Serverless Day - Online
- Rilasciata la versione Beta 2 di Silverlight 2.0