Как я могу использовать WaitHandle в ожидании завершения асинхронного вызова?

Рассмотрим этот код:

class Program
        {
            static void Main(string[] args)
            {
                Master master = new Master();
                master.Execute();
            }
        }

    class TestClass
    {
        public void Method(string s)
        {
            Console.WriteLine(s);
            Thread.Sleep(5000);
            Console.WriteLine("End Method()");
        }
    }
    class Master
    {
        private readonly TestClass test = new TestClass();

        public void Execute()
        {
            Console.WriteLine("Start main thread..");
            Action<String> act = test.Method;
            IAsyncResult res = act.BeginInvoke("Start Method()..", x =>
            {
                Console.WriteLine("Start Callback..");
                act.EndInvoke(x);
                Console.WriteLine("End Callback");
            }, null);
            Console.WriteLine("End main thread");
            Console.ReadLine();
        }
    }

Имеем результат:

Start main thread..
End main thread
Start Method()..
End Method()
Start Callback..
End Callback

Итак, я хочу результат:

Start main thread..    
Start Method()..
End Method()
Start Callback..
End Callback
End main thread

Как я могу ждать async в этом коде? Я проверил статью MSDN "Асинхронный вызов синхронных методов" и нашел следующее:

После вызова BeginInvoke вы можете сделать следующее:

  • Выполните некоторую работу, а затем вызовите EndInvoke для блокировки, пока вызов не завершится.
  • Получите WaitHandle с помощью свойства IAsyncResultAsyncWaitHandle
    , используйте его метод WaitOne для блокировки выполнения до тех пор, пока
    не будет передан сигнал WaitHandle, а затем вызовите EndInvoke.
  • Опросите IAsyncResult, возвращенный BeginInvoke, чтобы определить, когда асинхронный вызов завершился, а затем вызовите EndInvoke.
  • Передайте делегата для метода обратного вызова в BeginInvoke. Метод выполняется в потоке ThreadPool после завершения асинхронного вызова. Метод обратного вызова вызывает EndInvoke.

Я думаю, что лучший вариант для меня это второй. Но как это реализовать? В частности меня интересует перегрузка WaitOne() (Блокирует текущий потока до тех пор, пока текущий WaitHandle не получит сигнал). Как правильно это сделать? Я имею в виду общую схему в этом случае.

ОБНОВЛЕНИЕ:

Теперь я использую Task<T>:

 class Program
    {
        static void Main(string[] args)
        {
            Master master = new Master();
            master.Execute();
        }
    }

    class WebService
    {
        public int GetResponse(int i)
        {
            Random rand = new Random();
            i = i + rand.Next();
            Console.WriteLine("Start GetResponse()");
            Thread.Sleep(3000);
            Console.WriteLine("End GetResponse()");
            return i;
        }

        public void SomeMethod(List<int> list)
        {
            //Some work with list        
            Console.WriteLine("List.Count = {0}", list.Count);
        }
    }
    class Master
    {
        private readonly WebService webService = new WebService();

        public void Execute()
        {
            Console.WriteLine("Start main thread..");
            List<int> listResponse = new List<int>();
            for (int i = 0; i < 5; i++)
            {
                var task = Task<int>.Factory.StartNew(() => webService.GetResponse(1))
                    .ContinueWith(x =>
                    {
                        Console.WriteLine("Start Callback..");
                        listResponse.Add(x.Result);
                        Console.WriteLine("End Callback");
                    });
            }
            webService.SomeMethod(listResponse);
            Console.WriteLine("End main thread..");
            Console.ReadLine();
        }
    }

Основная проблема в том, что SomeMethod() становится пустым list.

Результат: введите здесь описание изображения

Теперь у меня чудовищное решение :(

 public void Execute()
        {
            Console.WriteLine("Start main thread..");
            List<int> listResponse = new List<int>();
            int count = 0;
            for (int i = 0; i < 5; i++)
            {
                var task = Task<int>.Factory.StartNew(() => webService.GetResponse(1))
                    .ContinueWith(x =>
                    {
                        Console.WriteLine("Start Callback..");
                        listResponse.Add(x.Result);
                        Console.WriteLine("End Callback");
                        count++;
                        if (count == 5)
                        {
                            webService.SomeMethod(listResponse);
                        }
                    });

            }
            Console.WriteLine("End main thread..");
            Console.ReadLine();
        }

Результат:

введите здесь описание изображения

Вот что мне нужно дождаться асинхронного вызова. Как я могу использовать Wait вместо Task здесь?

ОБНОВЛЕНИЕ 2:

class Master
{
    private readonly WebService webService = new WebService();
    public delegate int GetResponseDelegate(int i);

    public void Execute()
    {
        Console.WriteLine("Start main thread..");
        GetResponseDelegate act = webService.GetResponse;
        List<int> listRequests = new List<int>();
        for (int i = 0; i < 5; i++)
        {
            act.BeginInvoke(1, (result =>
            {                    
                int req = act.EndInvoke(result);
                listRequests.Add(req);
            }), null);  
        }

        webService.SomeMethod(listRequests);
        Console.WriteLine("End main thread..");
        Console.ReadLine();
    }
}

person Alexandr    schedule 13.01.2013    source источник


Ответы (2)


Я предполагаю, что это то, что вы хотите. Мы запускаем задачи и затем ждем их завершения, после того, как все выполнено, мы получаем результаты.

class Program
{
    static void Main(string[] args)
    {
        Master master = new Master();
        master.Execute();
    }
}

class WebService
{
    public int GetResponse(int i)
    {
        Random rand = new Random();
        i = i + rand.Next();
        Console.WriteLine("Start GetResponse()");
        Thread.Sleep(3000);
        Console.WriteLine("End GetResponse()");
        return i;
    }

    public void SomeMethod(List<int> list)
    {
        //Some work with list        
        Console.WriteLine("List.Count = {0}", list.Count);
    }
}
class Master
{
    private readonly WebService webService = new WebService();
    public void Execute()
    {
        Console.WriteLine("Start main thread..");
        var taskList = new List<Task<int>>();
        for (int i = 0; i < 5; i++)
        {
            Task<int> task = Task.Factory.StartNew(() => webService.GetResponse(1));
            taskList.Add(task);
        }

        Task<List<int>> continueWhenAll =
            Task.Factory.ContinueWhenAll(taskList.ToArray(),
                            tasks => tasks.Select(task => task.Result).ToList());

        webService.SomeMethod(continueWhenAll.Result);
        Console.WriteLine("End main thread..");
        Console.ReadLine();
    }

}

Некрасивое решение с BeginIvoke/EndInvoke

public void Execute()
{
    Func<int, int> func = webService.GetResponce;

    var countdownEvent = new CountdownEvent(5);
    var res = new List<int>();
    for (int i = 0; i < 5; ++i)
    {
        func.BeginInvoke(1, ar =>
                        {
                            var asyncDelegate = (Func<int, int>)((AsyncResult)ar).AsyncDelegate;
                            int ii = asyncDelegate.EndInvoke(ar);
                            res.Add(ii);
                            ((CountdownEvent)((AsyncResult)ar).AsyncState).Signal();
                        }, countdownEvent);
    }
    countdownEvent.Wait();
    Console.WriteLine(res.Count);
}
person Hamlet Hakobyan    schedule 13.01.2013
comment
Спасибо за ответ! Кажется, это то, что мне нужно! :) - person Alexandr; 14.01.2013
comment
Гамлет, не могли бы вы проверить мое обновление 2 в посте? Меня интересуют академические цели, если использовать async BeginInvoke(), можно ли добиться того же результата, что и в вашем ответе? - person Alexandr; 14.01.2013

Из вашего примера кода неясно, зачем вам нужен асинхронный вызов. Если вы этого не сделаете, вы можете просто вызвать test.Method(); синхронно.

Предполагая, что вам нужно асинхронное выполнение, не беспокойтесь об устаревших Delegate.BeginInvoke вещах. Используйте новый API на основе Task:

var task = Task.Factory.StartNew(() => test.Method());

Затем вы можете Wait выполнить задачу или await ее, или использовать Task.ContinueWith (выберите подходящую технику для вашего случая).

person usr    schedule 13.01.2013
comment
Уср, спасибо за отзыв! Действительно, я дал плохие образцы кода. Работаю с Windows Phone 7.5 и асинхронными запросами к веб-сервису. В примерах кода я скрываю этот момент, потому что мне нужен простой код в примере. Обновление для моего поста имеет наиболее похожий на реальность код. И я использую API на основе задач, он доступен в Windows Phone 7 :) - person Alexandr; 13.01.2013
comment
Теперь это совсем другой вопрос... Похоже, вам нужен хороший учебник о том, как работает параллелизм. Ваш код небезопасен, и вы можете извлечь выгоду из нескольких лучших практик. Рекомендую осваивать основы, используя обучающий материал. - person usr; 13.01.2013