Per visualizzare l'articolo in inglese, selezionare la casella di controllo Inglese. È possibile anche visualizzare il testo inglese in una finestra popup posizionando il puntatore del mouse sopra il testo.
Traduzione
Inglese

Calling Synchronous Methods Asynchronously

 

.NET Framework consente di chiamare qualsiasi metodo in modo asincrono. A questo scopo occorre definire un delegato con la stessa firma del metodo che si vuole chiamare. Common Language Runtime definisce automaticamente i metodi BeginInvoke e EndInvoke per il delegato, con le firme appropriate.

System_CAPS_noteNota

Le chiamate asincrone dei delegati, con particolare riferimento ai metodi BeginInvoke e EndInvoke, non sono supportate in .NET Compact Framework.

Il BeginInvoke metodo avvia la chiamata asincrona. Presenta gli stessi parametri del metodo da eseguire in modo asincrono, con due parametri aggiuntivi facoltativi. Il primo parametro è un delegato AsyncCallback che fa riferimento a un metodo da chiamare al completamento della chiamata asincrona. Il secondo parametro è un oggetto definito dall'utente che passa informazioni al metodo di callback. BeginInvoke restituisce immediatamente un valore e non attende il completamento della chiamata asincrona. BeginInvoke restituisce un oggetto IAsyncResult che può essere usato per monitorare lo stato di avanzamento della chiamata asincrona.

Il metodo EndInvoke recupera i risultati della chiamata asincrona. Può essere chiamato in qualsiasi momento dopo BeginInvoke. Se la chiamata asincrona non è stata completata, EndInvoke blocca il thread chiamante fino al suo completamento. Nei parametri di EndInvoke sono inclusi quelli di tipo out e ref (<Out> ByRef e ByRef in Visual Basic) del metodo da eseguire in modo asincrono, oltre all'oggetto IAsyncResult restituito da BeginInvoke.

System_CAPS_noteNota

La funzionalità IntelliSense in Visual Studio 2005 visualizza i parametri di BeginInvoke e EndInvoke. Se non si usa Visual Studio o uno strumento analogo o se si usa C# con Visual Studio 2005, vedere Asynchronous Programming Model (APM) per una descrizione dei parametri definiti per tali metodi.

Gli esempi di codice in questo argomento presentano quattro modi comuni per usare BeginInvoke e EndInvoke per effettuare chiamate asincrone. Dopo avere chiamato BeginInvoke è possibile procedere nel modo seguente:

  • Eseguire alcune operazioni, quindi chiamare EndInvoke per bloccare l'esecuzione fino al completamento della chiamata.

  • Ottenere un oggetto WaitHandle mediante la proprietà IAsyncResult.AsyncWaitHandle, usare il relativo metodo WaitOne per bloccare l'esecuzione fino a quando non viene segnalato WaitHandle, quindi chiamare EndInvoke.

  • Eseguire il polling dell'oggetto IAsyncResult restituito da BeginInvoke per stabilire quando viene completata la chiamata, quindi chiamare EndInvoke.

  • Passare un delegato per un metodo di callback a BeginInvoke. Il metodo viene eseguito su un thread ThreadPool al completamento della chiamata asincrona. Il metodo di callback chiama EndInvoke.

System_CAPS_importantImportante

Indipendentemente dalla tecnica usata, chiamare sempre EndInvoke per completare la chiamata asincrona.

Gli esempi di codice seguenti illustrano diversi modi per chiamare lo stesso metodo di lunga durata, TestMethod, in modo asincrono. Il metodo TestMethod visualizza un messaggio di console per indicare che ha iniziato l'elaborazione, si disattiva per alcuni secondi e quindi termina. TestMethod include un parametro out per dimostrare in che modo tali parametri vengono aggiunti alle firme di BeginInvoke e EndInvoke. I parametri ref possono essere gestiti in modo analogo.

L'esempio di codice seguente illustra la definizione di TestMethod e il delegato denominato AsyncMethodCaller che può essere usato per chiamare TestMethod in modo asincrono. Per compilare gli esempi di codice, è necessario includere le definizioni per TestMethod e il delegato AsyncMethodCaller.

using System;
using System.Threading; 

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
    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 = Thread.CurrentThread.ManagedThreadId;
            return String.Format("My call time was {0}.", callDuration.ToString());
        }
    }
    // The delegate must have the same signature as the method
    // it will call asynchronously.
    public delegate string AsyncMethodCaller(int callDuration, out int threadId);
}

Il modo più semplice per eseguire un metodo in modo asincrono consiste nell'avviare l'esecuzione del metodo chiamando il metodo BeginInvoke del delegato, eseguire alcune operazioni sul thread principale e quindi chiamare il metodo EndInvoke del delegato. EndInvoke potrebbe bloccare il thread chiamante perché non restituisce un valore fino al completamento della chiamata asincrona. Si tratta di una tecnica valida per le operazioni su file o rete.

System_CAPS_importantImportante

Poiché EndInvoke potrebbe bloccarsi, non deve mai essere chiamato dai thread che servono l'interfaccia utente.

using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
    public class AsyncMain 
    {
        public static void Main() 
        {
            // The asynchronous method puts the thread id here.
            int threadId;

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

            // Create the delegate.
            AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

            // Initiate the asychronous call.
            IAsyncResult result = caller.BeginInvoke(3000, 
                out threadId, null, null);

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

            // Call EndInvoke to wait for the asynchronous call to complete,
            // and to retrieve the results.
            string returnValue = caller.EndInvoke(out threadId, result);

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

/* This example produces output similar to the following:

Main thread 1 does some work.
Test method begins.
The call executed on thread 3, with return value "My call time was 3000.".
 */

È possibile ottenere un oggetto WaitHandle usando la proprietà AsyncWaitHandle dell'oggetto IAsyncResult restituito da BeginInvoke. L'oggetto WaitHandle viene segnalato al completamento della chiamata asincrona ed è possibile attenderlo chiamando il metodo WaitOne.

Se si usa un WaitHandle, è possibile eseguire altre operazioni prima o dopo il completamento della chiamata asincrona, ma prima di chiamare EndInvoke per recuperare i risultati.

System_CAPS_noteNota

L'handle di attesa non viene chiuso automaticamente quando si chiama EndInvoke. Se si rilasciano tutti i riferimenti all'handle di attesa, le risorse di sistema vengono liberate quando Garbage Collection recupera l'handle di attesa. Per liberare le risorse di sistema non appena si ha finito di usare l'handle di attesa, eliminarlo chiamando il metodo WaitHandle.Close. Garbage Collection opera in modo più efficiente quando gli oggetti eliminabili vengono eliminati in modo esplicito.

using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
    public class AsyncMain 
    {
        static void Main() 
        {
            // The asynchronous method puts the thread id here.
            int threadId;

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

            // Create the delegate.
            AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

            // Initiate the asychronous call.
            IAsyncResult result = caller.BeginInvoke(3000, 
                out threadId, null, null);

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

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

            // Perform additional processing here.
            // Call EndInvoke to retrieve the results.
            string returnValue = caller.EndInvoke(out threadId, result);

            // Close the wait handle.
            result.AsyncWaitHandle.Close();

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

/* This example produces output similar to the following:

Main thread 1 does some work.
Test method begins.
The call executed on thread 3, with return value "My call time was 3000.".
 */

È possibile usare la proprietà IsCompleted dell'oggetto IAsyncResult restituito da BeginInvoke per rilevare il completamento della chiamata asincrona. Questa operazione può essere eseguita quando si effettua la chiamata asincrona da un thread che serve l'interfaccia utente. Il polling del completamento consente al thread chiamante di continuare l'esecuzione mentre viene eseguita la chiamata asincrona su un thread ThreadPool.

using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
    public class AsyncMain 
    {
        static void Main() {
            // The asynchronous method puts the thread id here.
            int threadId;

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

            // Create the delegate.
            AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

            // Initiate the asychronous call.
            IAsyncResult result = caller.BeginInvoke(3000, 
                out threadId, null, null);

            // Poll while simulating work.
            while(result.IsCompleted == false) {
                Thread.Sleep(250);
                Console.Write(".");
            }

            // Call EndInvoke to retrieve the results.
            string returnValue = caller.EndInvoke(out threadId, result);

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

/* This example produces output similar to the following:

Test method begins.
.............
The call executed on thread 3, with return value "My call time was 3000.".
 */

Se il thread che avvia la chiamata asincrona non deve necessariamente essere il thread che elabora i risultati, è possibile eseguire un metodo di callback al completamento della chiamata. Il metodo di callback viene eseguito su un thread ThreadPool.

Per usare un metodo di callback, è necessario passare a BeginInvoke un delegato AsyncCallback che rappresenta il metodo di callback. È possibile passare anche un oggetto contenente informazioni che devono essere usate dal metodo di callback. Nel metodo di callback è possibile eseguire il cast di IAsyncResult, che è l'unico parametro del metodo di callback, a un oggetto AsyncResult. È quindi possibile usare la proprietà AsyncResult.AsyncDelegate per ottenere il delegato usato per avviare la chiamata in modo che sia possibile chiamare EndInvoke.

Note sull'esempio:

  • Il parametro threadId di TestMethod è un parametro out (<Out> ByRef in Visual Basic), pertanto il valore di input non viene mai utilizzato da TestMethod. Viene passata una variabile fittizia alla chiamata BeginInvoke. Se il parametro threadId fosse un parametro ref (ByRef in Visual Basic), la variabile dovrebbe essere un campo a livello di classe perché possa essere passata sia a BeginInvoke che a EndInvoke.

  • Le informazioni sullo stato che vengono passate a BeginInvoke sono costituite da una stringa di formato, che viene usata dal metodo di callback per formattare un messaggio di output. Dato che le informazioni sullo stato vengono passate come tipo Object, è necessario eseguirne il cast al tipo appropriato prima di poterle usare.

  • Il callback viene eseguito su un thread ThreadPool. I thread ThreadPool sono thread in background, che non mantengono l'applicazione in esecuzione se il thread principale termina, quindi il thread principale dell'esempio deve rimanere sospeso per un tempo sufficiente al completamento del callback.

using System;
using System.Threading;
using System.Runtime.Remoting.Messaging;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
    public class AsyncMain 
    {
        static void Main() 
        {
            // Create an instance of the test class.
            AsyncDemo ad = new AsyncDemo();

            // Create the delegate.
            AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

            // The threadId parameter of TestMethod is an out parameter, so
            // its input value is never used by TestMethod. Therefore, a dummy
            // variable can be passed to the BeginInvoke call. If the threadId
            // parameter were a ref parameter, it would have to be a class-
            // level field so that it could be passed to both BeginInvoke and 
            // EndInvoke.
            int dummy = 0;

            // Initiate the asynchronous call, passing three seconds (3000 ms)
            // for the callDuration parameter of TestMethod; a dummy variable 
            // for the out parameter (threadId); the callback delegate; and
            // state information that can be retrieved by the callback method.
            // In this case, the state information is a string that can be used
            // to format a console message.
            IAsyncResult result = caller.BeginInvoke(3000,
                out dummy, 
                new AsyncCallback(CallbackMethod),
                "The call executed on thread {0}, with return value \"{1}\".");

            Console.WriteLine("The main thread {0} continues to execute...", 
                Thread.CurrentThread.ManagedThreadId);

            // The callback is made on a ThreadPool thread. ThreadPool threads
            // are background threads, which do not keep the application running
            // if the main thread ends. Comment out the next line to demonstrate
            // this.
            Thread.Sleep(4000);

            Console.WriteLine("The main thread ends.");
        }

        // The callback method must have the same signature as the
        // AsyncCallback delegate.
        static void CallbackMethod(IAsyncResult ar) 
        {
            // Retrieve the delegate.
            AsyncResult result = (AsyncResult) ar;
            AsyncMethodCaller caller = (AsyncMethodCaller) result.AsyncDelegate;

            // Retrieve the format string that was passed as state 
            // information.
            string formatString = (string) ar.AsyncState;

            // Define a variable to receive the value of the out parameter.
            // If the parameter were ref rather than out then it would have to
            // be a class-level field so it could also be passed to BeginInvoke.
            int threadId = 0;

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

            // Use the format string to format the output message.
            Console.WriteLine(formatString, threadId, returnValue);
        }
    }
}

/* This example produces output similar to the following:

The main thread 1 continues to execute...
Test method begins.
The call executed on thread 3, with return value "My call time was 3000.".
The main thread ends.
 */
Mostra: