递归异步HttpWebRequests

时间:2011-03-25 11:51:21

标签: c# silverlight-4.0 asynchronous recursion httpwebrequest

假设我有以下课程:

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 个答案:

答案 0 :(得分:1)

你需要的只是一个线程同步的东西。我选择ManualResetEvent

但是,我没有看到使用异步IO的意义,因为您总是在开始新请求之前等待请求完成。但这个例子可能不会显示整个故事?

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();
   }
}

答案 1 :(得分:1)

当一个操作与下一个操作之间存在一些顺序依赖时,特别是在异步编码的情况下并不容易。这就是我写AsyncOperationService来处理的确切问题,它是一个非常简短的代码。

首先给你一点点阅读:Simple Asynchronous Operation Runner – Part 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回调中清除它