Información general acerca de la programación asincrónica

.NET Framework permite llamar a cualquier método de forma asincrónica. Defina un delegado con la misma firma que el método al que desee llamar; Common Language Runtime define automáticamente los métodos BeginInvoke y EndInvoke para este delegado, con las firmas adecuadas.

El método BeginInvoke se utiliza para iniciar la llamada asincrónica. Tiene los mismos parámetros que el método que desea ejecutar asincrónicamente, además de dos parámetros adicionales, que se describirán más adelante. BeginInvoke regresa inmediatamente y no espera a que se complete la llamada asincrónica. BeginInvoke devuelve un resultado IasyncResult, que puede utilizarse para supervisar el progreso de la llamada.

El método BeginInvoke se utiliza para recuperar los resultados de la llamada asincrónica. Se puede llamar a este método en cualquier momento, siempre y cuando se haya llamado al método BeginInvoke; si la llamada asincrónica no ha terminado, el método EndInvoke se bloqueará hasta que termine dicha llamada. Entre los parámetros de EndInvoke se incluyen los parámetros out y ref (<Out> ByRef y ByRef en Visual Basic) del método que desea ejecutar asincrónicamente, además del resultado IAsyncResult devuelto por BeginInvoke.

Nota   La función IntelliSense de Visual Studio .NET muestra los parámetros de BeginInvoke y EndInvoke. Si no está utilizando Visual Studio o una herramienta similar, o si está utilizando C# con Visual Studio .NET, vea Firmas de métodos asincrónicos para obtener una descripción de los parámetros que el motor de tiempo de ejecución define para estos métodos.

El código de este tema muestra cuatro de las formas más comunes de utilizar los métodos BeginInvoke y EndInvoke para realizar llamadas asincrónicas. Una vez que ha llamado al método BeginInvoke puede:

  • Realizar algo de trabajo y, después, llamar al método EndInvoke para que se bloquee hasta que termine la llamada.

  • Obtener un controlador WaitHandle utilizando IAsyncResult.AsyncWaitHandle, utilizar su método WaitOne para bloquear la ejecución hasta que se señalice WaitHandle, y después llamar al método EndInvoke.

  • Sondear el resultado IAsyncResult devuelto por BeginInvoke para determinar el momento de finalización de la llamada asincrónica y, después, llamar al método EndInvoke.

  • Pasar un delegado para un método de devolución de llamada a BeginInvoke. El método se ejecuta en un subproceso ThreadPool una vez que ha finalizado la llamada asincrónica y se puede llamar al método EndInvoke.

    PRECAUCIÓN   Llame siempre al método EndInvoke una vez que haya finalizado la llamada asincrónica.

Método de prueba y delegado asincrónico

En los cuatro ejemplos se utiliza el mismo método de prueba de ejecución larga: TestMethod. Este método muestra un mensaje en la consola para poner de manifiesto que ha comenzado el procesamiento, permanece inactivo durante unos segundos y, a continuación, finaliza. TestMethod tiene un parámetro out (<Out> ByRef en Visual Basic) que muestra la forma en que dichos parámetros se agregan a las firmas de BeginInvoke y EndInvoke. Puede controlar los parámetros ref (ByRef en Visual Basic) de forma similar.

En el siguiente ejemplo de código se muestra el método TestMethod y el delegado que representa a este método; para utilizar cualquiera de los ejemplos, anexe el código de ejemplo a este código.

Nota   Para simplificar los ejemplos, TestMethod se ha declarado en una clase independiente de Main(). TestMethod puede ser también un método static (Shared en Visual Basic) en la misma clase que la que contiene a Main().

Imports System
Imports System.Threading
Imports System.Runtime.InteropServices 

Public Class AsyncDemo 
    ' The method to be executed asynchronously.
    '
    Public Function TestMethod(ByVal callDuration As Integer, _
            <Out> ByRef threadId As Integer) As String
        Console.WriteLine("Test method begins.")
        Thread.Sleep(callDuration)
        threadId = AppDomain.GetCurrentThreadId()
        return "MyCallTime was " + callDuration.ToString()
    End Function
End Class

' The delegate must have the same signature as the method
' you want to call asynchronously.
Public Delegate Function AsyncDelegate(ByVal callDuration As Integer, _
    <Out> ByRef threadId As Integer) As String
[C#]
using System;
using System.Threading; 

public class AsyncDemo {
    // The method to be executed asynchronously.
    //
    public string TestMethod(int callDuration, out int threadId) {
        Console.WriteLine("Test method begins.");
        Thread.Sleep(callDuration);
        threadId = AppDomain.GetCurrentThreadId();
        return "MyCallTime was " + callDuration.ToString();
    }
}

// The delegate must have the same signature as the method
// you want to call asynchronously.
public delegate string AsyncDelegate(int callDuration, out int threadId);

Esperar una llamada asincrónica con EndInvoke

La forma más sencilla de ejecutar un método asincrónicamente es comenzar con el método BeginInvoke, realizar algo de trabajo en el subproceso principal y, después, llamar al método EndInvoke. EndInvoke no regresa hasta que la llamada asincrónica finaliza. Se trata de una buena técnica para utilizarla con operaciones de archivos o de redes, pero como se bloquea en EndInvoke, no debe utilizarla desde subprocesos que dan servicio a la interfaz de usuario.

Public Class AsyncMain 
    Shared Sub Main() 
        ' The asynchronous method puts the thread id here.
        Dim threadId As Integer

        ' Create an instance of the test class.
        Dim ad As New AsyncDemo()

        ' Create the delegate.
        Dim dlgt As New AsyncDelegate(AddressOf ad.TestMethod)
   
        ' Initiate the asynchronous call.
        Dim ar As IAsyncResult = dlgt.BeginInvoke(3000, _
            threadId, Nothing, Nothing)

        Thread.Sleep(0)
        Console.WriteLine("Main thread {0} does some work.", _
             AppDomain.GetCurrentThreadId())

        ' Call EndInvoke to Wait for the asynchronous call to complete,
        ' and to retrieve the results.
        Dim ret As String = dlgt.EndInvoke(threadId, ar)

        Console.WriteLine("The call executed on thread {0}, with return value ""{1}"".", threadId, ret)
    End Sub
End Class
[C#]
public class AsyncMain {
    static void Main(string[] args) {
        // The asynchronous method puts the thread id here.
        int threadId;

        // Create an instance of the test class.
        AsyncDemo ad = new AsyncDemo();

        // Create the delegate.
        AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
   
        // Initiate the asychronous call.
        IAsyncResult ar = dlgt.BeginInvoke(3000, 
            out threadId, null, null);

        Thread.Sleep(0);
        Console.WriteLine("Main thread {0} does some work.",
            AppDomain.GetCurrentThreadId());

        // Call EndInvoke to Wait for the asynchronous call to complete,
        // and to retrieve the results.
        string ret = dlgt.EndInvoke(out threadId, ar);

        Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
    }
}

Esperar una llamada asincrónica con WaitHandle

Esperar en WaitHandle es una técnica de sincronización de subprocesos muy común. Puede obtener un WaitHandle utilizando la propiedad AsyncWaitHandle del IAsyncResult devuelto por BeginInvoke. WaitHandle se señaliza cuando la llamada asincrónica finaliza y puede esperar a que termine llamando a su WaitOne.

Si utiliza un WaitHandle, puede realizar otros procesamientos una vez finalizada la llamada asincrónica, pero antes de que recupere los resultados llamando al método EndInvoke.

Public Class AsyncMain 
    Shared Sub Main() 
        ' The asynchronous method puts the thread id here.
        Dim threadId As Integer

        ' Create an instance of the test class.
        Dim ad As New AsyncDemo()

        ' Create the delegate.
        Dim dlgt As New AsyncDelegate(AddressOf ad.TestMethod)
   
        ' Initiate the asynchronous call.
        Dim ar As IAsyncResult = dlgt.BeginInvoke(3000, 
            threadId, Nothing, Nothing)

        Thread.Sleep(0)
        Console.WriteLine("Main thread {0} does some work.",
            AppDomain.GetCurrentThreadId())

        ' Wait for the WaitHandle to become signaled.
        ar.AsyncWaitHandle.WaitOne()

        ' Perform additional processing here.
        ' Call EndInvoke to retrieve the results.
        Dim ret As String = dlgt.EndInvoke(threadId, ar)

        Console.WriteLine("The call executed on thread {0}, with return value ""{1}"".", threadId, ret)
    End Sub
End Class
[C#]
public class AsyncMain {
    static void Main(string[] args) {
        // The asynchronous method puts the thread id here.
        int threadId;

        // Create an instance of the test class.
        AsyncDemo ad = new AsyncDemo();

        // Create the delegate.
        AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
   
        // Initiate the asychronous call.
        IAsyncResult ar = dlgt.BeginInvoke(3000, 
            out threadId, null, null);

        Thread.Sleep(0);
        Console.WriteLine("Main thread {0} does some work.",
            AppDomain.GetCurrentThreadId());

        // Wait for the WaitHandle to become signaled.
        ar.AsyncWaitHandle.WaitOne();

        // Perform additional processing here.
        // Call EndInvoke to retrieve the results.
        string ret = dlgt.EndInvoke(out threadId, ar);

        Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
    }
}

Sondear la finalización de una llamada asincrónica

Puede utilizar la propiedad IsCompleted del IAsyncResult devuelto por BeginInvoke para descubrir el momento en que finaliza la llamada asincrónica. Puede hacer esto último cuando realice la llamada asincrónica desde un subproceso que dé servicio a la interfaz de usuario. El sondeo de finalización permite que el subproceso de la interfaz de usuario siga procesando los datos proporcionados por el usuario.

Public Class AsyncMain 
    Shared Sub Main() 
        ' The asynchronous method puts the thread id here.
        Dim threadId As Integer

        ' Create an instance of the test class.
        Dim ad As New AsyncDemo()

        ' Create the delegate.
        Dim dlgt As New AsyncDelegate(AddressOf ad.TestMethod)
   
        ' Initiate the asynchronous call.
        Dim ar As IAsyncResult = dlgt.BeginInvoke(3000, 
            threadId, Nothing, Nothing)

        ' Poll while simulating work.
        While ar.IsCompleted = False
            Thread.Sleep(10)
        End While

        ' Call EndInvoke to retrieve the results.
        Dim ret As String = dlgt.EndInvoke(threadId, ar)

        Console.WriteLine("The call executed on thread {0}, with return value ""{1}"".", threadId, ret)
    End Sub
End Class
[C#]
public class AsyncMain {
    static void Main(string[] args) {
        // The asynchronous method puts the thread id here.
        int threadId;

        // Create an instance of the test class.
        AsyncDemo ad = new AsyncDemo();

        // Create the delegate.
        AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
   
        // Initiate the asychronous call.
        IAsyncResult ar = dlgt.BeginInvoke(3000, 
            out threadId, null, null);

        // Poll while simulating work.
        while(ar.IsCompleted == false) {
            Thread.Sleep(10);
        }

        // Call EndInvoke to retrieve the results.
        string ret = dlgt.EndInvoke(out threadId, ar);

        Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
    }
}

Ejecutar un método de devolución de llamada cuando finaliza una llamada asincrónica

Si el subproceso que inicia la llamada asincrónica no necesita procesar los resultados, puede ejecutar un método de devolución de llamada cuando finalice la llamada. El método de devolución de llamada se ejecuta en un subproceso ThreadPool.

Para utilizar un método de devolución de llamada, debe pasar al método BeginInvoke un delegado de AsyncCallback que represente al método. También puede pasar un objeto que contenga la información que va a utilizar el método de devolución de llamada. Por ejemplo, puede pasar el delegado que se utilizó para iniciar la llamada, de modo que el método de devolución de llamada puede llamar al método EndInvoke.

Public Class AsyncMain 
    ' The asynchronous method puts the thread id here.
    Private Shared threadId As Integer
    
    Shared Sub Main() 
        ' Create an instance of the test class.
        Dim ad As New AsyncDemo()

        ' Create the delegate.
        Dim dlgt As New AsyncDelegate(AddressOf ad.TestMethod)
   
        ' Initiate the asynchronous call.
        Dim ar As IAsyncResult = dlgt.BeginInvoke(3000, _
            threadId, _
            AddressOf CallbackMethod, _
            dlgt)

        Console.WriteLine("Press Enter to close application.")
        Console.ReadLine()
    End Sub

    ' Callback method must have the same signature as the
    ' AsyncCallback delegate.
    Shared Sub CallbackMethod(ByVal ar As IAsyncResult)
        ' Retrieve the delegate.
        Dim dlgt As AsyncDelegate = CType(ar.AsyncState, AsyncDelegate)

        ' Call EndInvoke to retrieve the results.
        Dim ret As String = dlgt.EndInvoke(threadId, ar)

        Console.WriteLine("The call executed on thread {0}, with return value ""{1}"".", threadId, ret)
    End Sub
End Class
[C#]
public class AsyncMain {
    // Asynchronous method puts the thread id here.
    private static int threadId;

    static void Main(string[] args) {
        // Create an instance of the test class.
        AsyncDemo ad = new AsyncDemo();

        // Create the delegate.
        AsyncDelegate dlgt = new AsyncDelegate(ad.TestMethod);
   
        // Initiate the asychronous call.  Include an AsyncCallback
        // delegate representing the callback method, and the data
        // needed to call EndInvoke.
        IAsyncResult ar = dlgt.BeginInvoke(3000,
            out threadId, 
            new AsyncCallback(CallbackMethod),
            dlgt );

        Console.WriteLine("Press Enter to close application.");
        Console.ReadLine();
    }
    
    // Callback method must have the same signature as the
    // AsyncCallback delegate.
    static void CallbackMethod(IAsyncResult ar) {
        // Retrieve the delegate.
        AsyncDelegate dlgt = (AsyncDelegate) ar.AsyncState;

        // Call EndInvoke to retrieve the results.
        string ret = dlgt.EndInvoke(out threadId, ar);

        Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".", threadId, ret);
    }
}

Vea también

Programación asincrónica