Рекурсивный Асинхронный HttpWebRequests - PullRequest
1 голос
/ 25 марта 2011

Предположим, у меня есть следующий класс:

Public class FooBar
{

   List<Items> _items = new List<Items>();

   public List<Items> FetchItems(int parentItemId)
   {

     FetchSingleItem(int itemId);

     return _items
   }

   private void FetchSingleItem(int itemId)
   {

   Uri url = new Uri(String.Format("http://SomeURL/{0}.xml", itemId);
   HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);

   webRequest.BeginGetResponse(ReceiveResponseCallback, webRequest);

   }

   void ReceiveResponseCallback(IAsyncResult result)
   {

     // End the call and extract the XML from the response and add item to list

     _items.Add(itemFromXMLResponse);

     // If this item is linked to another item then fetch that item 


     if (anotherItemIdExists == true)
     {
        FetchSingleItem(anotherItemId);
     }


   }

}

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

Я хочу сделать первоначальный вызов FetchSingleItem, а затем дождаться завершения всех вызовов, а затем вернуть List<Items> к вызывающему коду.

Может ли кто-нибудь указать мне направильное направление?Я более чем рад провести рефакторинг всего этого в случае необходимости (что, я подозреваю, будет иметь место!)

Ответы [ 2 ]

1 голос
/ 26 марта 2011

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

Первое небольшое легкое чтение для вас: Простой асинхронный оператор Runner - часть 2 . Непременно прочитайте часть 1, но она немного тяжелее, чем я хотел. Все, что вам действительно нужно, это AsyncOperationService код из него.

Теперь в вашем случае вы должны преобразовать код извлечения во что-то вроде следующего.

 private IEnumerable<AsyncOperation> FetchItems(int startId)
 {
     XDocument itemDoc = null;
     int currentId = startId;

     while (currentID != 0)
     {
        yield return DownloadString(new Uri(String.Format("http://SomeURL/{0}.xml", currentId), UriKind.Absolute),
             itemXml => itemDoc = XDocument.Parse(itemXml) );

        // Do stuff with itemDoc like creating your item and placing it in the list.

        // Assign the next linked ID to currentId or if no other items assign 0

     }
 }

Обратите внимание, что в блоге также есть реализация DownloadString, которая, в свою очередь, использует WebClient, что упрощает работу. Однако принципы все еще применяются, если по какой-то причине вы должны придерживаться HttpWebRequest. (Дайте мне знать, если у вас возникли проблемы при создании AsyncOperation для этого)

Затем вы использовали бы этот код следующим образом: -

int startId = GetSomeIDToStartWith();
Foo myFoo = new Foo();

myFoo.FetchItems(startId).Run((err) =>
{
    // Clear IsBusy
    if (err == null)
    {
        // All items are now fetched continue doing stuff here.

    }
    else
    {
        // "Oops something bad happened" code here
    }
}
// Set IsBusy 

Обратите внимание, что вызов Run является асинхронным, выполнение кода будет перепрыгивать через него до того, как будут выбраны все элементы. Если пользовательский интерфейс бесполезен для пользователя или даже опасен, вам необходимо заблокировать его дружественным способом. Лучший способ (IMO) сделать это с помощью элемента управления BusyIndicator из набора инструментов, установить его свойство IsBusy после вызова на Run и очистить его в обратном вызове Run.

1 голос
/ 25 марта 2011

Все, что вам нужно, это синхронизация потоков.Я выбрал ManualResetEvent.

Однако я не вижу смысла в использовании асинхронного ввода-вывода, поскольку вы всегда ждете завершения запроса, прежде чем начинать новый.Но пример не может показать всю историю?

Public class FooBar
{
   private ManualResetEvent _completedEvent = new ManualResetEvent(false);
   List<Items> _items = new List<Items>();

   public List<Items> FetchItems(int parentItemId)
   {
      FetchSingleItem(itemId);
      _completedEvent.WaitOne();
      return _items
   }

   private void FetchSingleItem(int itemId)
   {
       Uri url = new Uri(String.Format("http://SomeURL/{0}.xml", itemId);
       HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(url);

       webRequest.BeginGetResponse(ReceiveResponseCallback, webRequest);
   }

   void ReceiveResponseCallback(IAsyncResult result)
   {
        // End the call and extract the XML from the response and add item to list

        _items.Add(itemFromXMLResponse);

        // If this item is linked to another item then fetch that item 


        if (anotherItemIdExists == true)
        {
            FetchSingleItem(anotherItemId);
        }
        else
            _completedEvent.Set();
   }
}
...