Чтобы прочитать статью на английском языке, установите флажок Английский. Вы также можете просматривать текст на английском языке во всплывающем окне, наводя указатель мыши на текст.
Перевод
Английский

Calling Synchronous Methods Asynchronously

 

В .NET Framework можно асинхронно вызывать любой метод. Для этого необходимо определить делегат с той же сигнатурой, что и у вызываемого метода. Среда CLR автоматически определяет для этого делегата методы BeginInvoke и EndInvoke с нужными сигнатурами.

System_CAPS_noteПримечание

Асинхронные вызовы делегатов, в частности методы BeginInvoke и EndInvoke, не поддерживаются в платформе .NET Compact Framework.

Вызван метод Collect. Он имеет все те же параметры, что и метод, который нужно выполнить асинхронно, а также два дополнительных необязательных параметра. Первый параметр является делегатом AsyncCallback, который ссылается на метод, вызываемый при завершении асинхронного вызова. Второй параметр — это определяемый пользователем объект, который передает данные в метод обратного вызова. Автоматизация пользовательского интерфейса также позволяет скриптам автоматических тестов взаимодействовать с UI. Автоматизация пользовательского интерфейса также позволяет скриптам автоматических тестов взаимодействовать с UI.

Вызван метод Collect. Его можно вызвать в любое время после вызова метода BeginInvoke. Если асинхронный вызов не завершен, метод EndInvoke блокирует вызывающий поток до завершения вызова. Список параметров метода EndInvoke включает параметры out и ref (<Out> ByRef и ByRef в Visual Basic) метода, который требуется вызвать асинхронно, а также значение IAsyncResult, возвращаемое методом BeginInvoke.

System_CAPS_noteПримечание

Функция IntelliSense в Visual Studio 2005 отображает параметры методов BeginInvoke и EndInvoke. Если вы не используете Visual Studio или похожий инструмент либо если используется C# вместе с Visual Studio 2005, см. статью Asynchronous Programming Model (APM) с описанием параметров, определенных для этих методов.

В приведенных в этой статье примерах кода демонстрируется четыре основных способа использования методов BeginInvoke и EndInvoke для выполнения асинхронных вызовов. После вызова метода BeginInvoke можно выполнять следующие действия.

  • Выполнить какие-либо операции, а затем вызвать метод EndInvoke для блокировки потока, пока не будет завершен вызов.

  • Получить объект WaitHandle с помощью свойства IAsyncResult.AsyncWaitHandle, использовать метод WaitOne для блокировки выполнения до получения сигнала WaitHandle, а затем вызвать метод EndInvoke.

  • Периодически опрашивать интерфейс IAsyncResult, возвращаемый методом BeginInvoke, для определения момента завершения асинхронного вызова, а затем вызвать метод EndInvoke.

  • Передать в метод BeginInvoke делегат для метода обратного вызова. Этот метод выполняется для потока ThreadPool после завершения асинхронного вызова. Метод обратного вызова вызывает метод EndInvoke.

System_CAPS_importantВажно

Независимо от выбранного варианта необходимо всегда использовать для завершения асинхронного вызова метод EndInvoke.

В приведенных ниже примерах кода показаны различные способы асинхронного вызова одного и того же длительно выполняющегося метода TestMethod. Ядро автоматизации UI маскирует любые отличия в структурах, принадлежащих различным частям UI. Автоматизация пользовательского интерфейса также позволяет скриптам автоматических тестов взаимодействовать с UI. Таким же способом можно обрабатывать параметры ref.

В следующем примере кода показано определение TestMethod и делегата AsyncMethodCaller, который может использоваться для асинхронного вызова TestMethod. Чтобы скомпилировать эти примеры кода, необходимо включить определения для метода TestMethod и делегата 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);
}

Самым простым способом асинхронного вызова метода является запуск выполнения метода посредством вызова метода BeginInvoke делегата, выполнения каких-либо действий в основном потоке и последующего вызова метода EndInvoke. Автоматизация пользовательского интерфейса также позволяет скриптам автоматических тестов взаимодействовать с UI. Этот подход хорошо использовать с файловыми и сетевыми операциями.

System_CAPS_importantВажно

Поскольку метод EndInvoke может заблокировать поток, его ни в коем случае нельзя вызывать из потоков, обслуживающих пользовательский интерфейс.

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.".
 */

Объект WaitHandle можно получить с помощью свойства AsyncWaitHandle интерфейса IAsyncResult, возвращаемого методом BeginInvoke. Ядро автоматизации UI маскирует любые отличия в структурах, принадлежащих различным частям UI.

При использовании объекта WaitHandle можно выполнять дополнительные операции до или после завершения асинхронного вызова, но до вызова метода EndInvoke для получения результатов.

System_CAPS_noteПримечание

При вызове метода EndInvoke дескриптор ожидания не закрывается автоматически. Если удалить все ссылки на дескриптор ожидания, системные ресурсы будут освобождены при удалении дескриптора ожидания сборщиком мусора. Чтобы освободить системные ресурсы сразу после завершения использования дескриптора ожидания, удалите его, вызвав метод WaitHandle.Close. При явном удалении ненужных объектов сборщик мусора работает более эффективно.

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.".
 */

Свойство IsCompleted объекта IAsyncResult, возвращаемого методом BeginInvoke, можно использовать для отслеживания завершения асинхронного вызова. Это можно делать, когда асинхронный вызов выполнен из потока, обслуживающего пользовательский интерфейс. Опрос завершения позволяет вызывающему потоку продолжить выполнение при асинхронном вызове для потока 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.".
 */

Если поток, инициировавший асинхронный вызов, не обязательно должен быть потоком, обрабатывающим результаты вызова, после завершения асинхронного вызова можно выполнить метод обратного вызова. Метод обратного вызова выполняется для потока ThreadPool.

Чтобы использовать метод обратного вызова, необходимо передать в метод BeginInvoke делегат AsyncCallback, который представляет метод обратного вызова. Кроме того, можно передать объект, содержащий данные, которые будут использоваться методом обратного вызова. В методе обратного вызова параметр IAsyncResult, который является единственным параметром метода обратного вызова, можно привести к типу объекта AsyncResult. После этого свойство AsyncResult.AsyncDelegate можно будет использовать для получения делегата, с помощью которого инициирован вызов, чтобы можно было вызвать метод EndInvoke.

Примечания к примеру.

  • Вызван метод Collect. При вызове метода BeginInvoke ему передается фиктивная переменная. Если параметр threadId является параметром ref (ByRef в Visual Basic), переменная должна быть полем уровня класса, чтобы ее можно было передавать методам BeginInvoke и EndInvoke.

  • Данные о состоянии передаются методу BeginInvoke в виде строки форматирования, используемой методом обратного вызова для форматирования выходного сообщения. Поскольку данные о состоянии передаются в виде типа Object, перед использованием их необходимо привести к нужному типу.

  • Обратный вызов выполняется в потоке ThreadPool. Автоматизация пользовательского интерфейса также позволяет скриптам автоматических тестов взаимодействовать с UI.

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.
 */
Показ: