volatile (Referencia de C#)

La palabra clave volatile indica que un campo puede ser modificado por varios subprocesos que se ejecutan al mismo tiempo. El compilador, el sistema de runtime e incluso el hardware pueden reorganizar las lecturas y escrituras en las ubicaciones de memoria por motivos de rendimiento. Los campos declarados como volatile se excluyen de determinados tipos de optimizaciones. No hay ninguna garantía de una única ordenación total de las operaciones de escritura volátiles como se muestra en todos los subprocesos de ejecución. Para obtener más información, vea la clase Volatile.

Nota:

En un sistema de varios procesadores, una operación de lectura volátil no garantiza que ningún procesador obtenga el valor más reciente escrito en esa ubicación de memoria. De forma similar, una operación de escritura volátil no garantiza que el valor escrito sea inmediatamente visible para otros procesadores.

La palabra clave volatile se puede aplicar a campos de estos tipos:

  • Tipos de referencia.
  • Tipos de puntero (en un contexto no seguro). Observe que aunque el propio puntero puede ser volatile, no el objeto al que apunta. Es decir, no puede declarar un "puntero a volatile".
  • Tipos simples como sbyte, byte, short, ushort, int, uint, char, float y bool.
  • Un tipo enum con uno de los siguientes tipos base: byte, sbyte, short, ushort, int o uint.
  • Parámetros de tipo genérico que se sabe que son tipos de referencia.
  • IntPtr y UIntPtr.

Otros tipos, incluidos double y long, no pueden marcarse como volatile porque no se puede garantizar que las lecturas y escrituras los campos de esos tipos sean atómicas. Para proteger el acceso multiproceso a esos tipos de campos, use los miembros de clase Interlocked o proteja el acceso mediante la instrucción lock.

La palabra clave volatile solo se puede aplicar a campos de class o struct. Las variables locales no se pueden declarar como volatile.

Ejemplo

En el ejemplo siguiente se muestra cómo declarar una variable de campo pública como volatile.

class VolatileTest
{
    public volatile int sharedStorage;

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

En el ejemplo siguiente se muestra cómo crear un subproceso auxiliar o de trabajo y usarlo para realizar el procesamiento en paralelo con el del subproceso principal. Para más información sobre el multithreading, vea Subprocesamiento administrado.

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.
}

Con el modificador volatile que se agrega a la declaración de _shouldStop en su lugar, siempre obtendrá los mismos resultados (similar al fragmento que se muestra en el código anterior). Sin embargo, sin ese modificador en el miembro _shouldStop, el comportamiento es imprevisible. El método DoWork puede optimizar el acceso a los miembros, lo que provoca la lectura de datos obsoletos. Dada la naturaleza de la programación multiproceso, el número de lecturas obsoletas es imprevisible. Distintas ejecuciones del programa generarán resultados ligeramente diferentes.

Especificación del lenguaje C#

Para obtener más información, consulte la Especificación del lenguaje C#. La especificación del lenguaje es la fuente definitiva de la sintaxis y el uso de C#.

Vea también