volatile (Riferimenti per C#)

La parola chiave volatile indica che un campo potrebbe essere modificato da più thread eseguiti contemporaneamente. Il compilatore, il sistema di runtime e anche l'hardware possono riordinare le letture e le scritture in posizioni di memoria per motivi di prestazioni. I campi dichiarati volatile vengono esclusi da determinati tipi di ottimizzazioni. Non c'è garanzia di un singolo ordinamento totale delle scritture volatili come osservato da tutti i thread di esecuzione. Per altre informazioni, vedere la classe Volatile.

Nota

In un sistema multiprocessore, un'operazione di lettura volatile non garantisce di ottenere il valore più recente scritto in tale posizione di memoria da qualsiasi processore. Analogamente, un'operazione di scrittura volatile non garantisce che il valore scritto sia immediatamente visibile ad altri processori.

La parola chiave volatile può essere applicata ai campi di questi tipi:

  • Tipi di riferimento.
  • Tipi di puntatore (in un contesto non sicuro). Si noti che sebbene il puntatore in sé possa essere volatile, non può esserlo l'oggetto a cui punta. In altre parole, non è possibile dichiarare un "puntatore a volatile".
  • Tipi semplici come sbyte, byte, short, ushort, int, uint, char, float e bool.
  • Tipo enum con uno di questi tipi di base: byte, sbyte, short, ushort, int o uint.
  • Parametri di tipo generico noti come tipi di riferimento.
  • IntPtr e UIntPtr.

Altri tipi, inclusi double e long, non possono essere contrassegnati come volatile, perché non è possibile garantire che le letture e le scritture in campi di questi tipi siano atomiche. Per proteggere l'accesso multithread a questi tipi di campi, usare i membri della classe Interlocked o proteggere l'accesso usando l'istruzione lock.

La parola chiave volatile può essere applicata solo a campi di un oggetto class o struct. Le variabili locali non possono essere dichiarate volatile.

Esempio

Nell'esempio riportato di seguito viene illustrato come dichiarare volatile una variabile di campo pubblico.

class VolatileTest
{
    public volatile int sharedStorage;

    public void Test(int i)
    {
        sharedStorage = i;
    }
}

Nell'esempio seguente viene illustrato come un thread di lavoro o ausiliario può essere creato e usato per eseguire l'elaborazione in parallelo con quella del thread principale. Per altre informazioni sul multithreading, vedere Threading gestito.

public class Worker
{
    // This method is called when the thread is started.
    public void DoWork()
    {
        bool work = false;
        while (!_shouldStop)
        {
            work = !work; // simulate some work
        }
        Console.WriteLine("Worker thread: terminating gracefully.");
    }
    public void RequestStop()
    {
        _shouldStop = true;
    }
    // Keyword volatile is used as a hint to the compiler that this data
    // member is accessed by multiple threads.
    private volatile bool _shouldStop;
}

public class WorkerThreadExample
{
    public static void Main()
    {
        // Create the worker thread object. This does not start the thread.
        Worker workerObject = new Worker();
        Thread workerThread = new Thread(workerObject.DoWork);

        // Start the worker thread.
        workerThread.Start();
        Console.WriteLine("Main thread: starting worker thread...");

        // Loop until the worker thread activates.
        while (!workerThread.IsAlive)
            ;

        // Put the main thread to sleep for 500 milliseconds to
        // allow the worker thread to do some work.
        Thread.Sleep(500);

        // Request that the worker thread stop itself.
        workerObject.RequestStop();

        // Use the Thread.Join method to block the current thread
        // until the object's thread terminates.
        workerThread.Join();
        Console.WriteLine("Main thread: worker thread has terminated.");
    }
    // Sample output:
    // Main thread: starting worker thread...
    // Worker thread: terminating gracefully.
    // Main thread: worker thread has terminated.
}

Dopo aver aggiunto il modificatore volatile alla dichiarazione di _shouldStop, si otterranno sempre gli stessi risultati, analogamente all'estratto mostrato nel codice precedente. Tuttavia, senza il modificatore nel membro _shouldStop, il comportamento è imprevedibile. Il metodo DoWork può ottimizzare l'accesso ai membri, causando la lettura dei dati non aggiornati. A causa della natura della programmazione multithread, il numero di letture non aggiornate è imprevedibile. Esecuzioni diverse del programma produrranno risultati leggermente diversi.

Specifiche del linguaggio C#

Per altre informazioni, vedere la specifica del linguaggio C#. La specifica del linguaggio costituisce il riferimento ufficiale principale per la sintassi e l'uso di C#.

Vedi anche