C# 递归异步HttpWebRequests

C# 递归异步HttpWebRequests,c#,silverlight-4.0,asynchronous,recursion,httpwebrequest,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 it

假设我有以下课程:

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


   }

}
公共类FooBar
{
列表_items=新列表();
公共列表获取项(int parentItemId)
{
FetchSingleItem(int itemId);
退货项目
}
私有void FetchSingleItem(int-itemId)
{
Uri url=新的Uri(String.Format(“http://SomeURL/{0}.xml”,itemId);
HttpWebRequestWebRequest=(HttpWebRequest)HttpWebRequest.Create(url);
webRequest.BeginGetResponse(接收方回复,webRequest);
}
无效接收方响应回拨(IAsyncResult结果)
{
//结束调用并从响应中提取XML,然后将项添加到列表中
_items.Add(itemFromXMLResponse);
//如果此项链接到另一项,则获取该项
if(anotherItemIdExists==true)
{
FetchSingleItem(另一个项目ID);
}
}
}
可能有任意数量的链接项,我只能在运行时知道

我要做的是对
FetchSingleItem
进行初始调用,然后等待所有调用完成,然后将
List
返回调用代码


有人能给我指出正确的方向吗?如果需要的话,我非常乐意重构整个过程(我怀疑会是这样!)

您所需要的只是一个线程同步的东西。我选择了
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();
   }
}
公共类FooBar
{
private ManualResetEvent _completedEvent=新的ManualResetEvent(错误);
列表_items=新列表();
公共列表获取项(int parentItemId)
{
FetchSingleItem(itemId);
_completedEvent.WaitOne();
退货项目
}
私有void FetchSingleItem(int-itemId)
{
Uri url=新的Uri(String.Format(“http://SomeURL/{0}.xml”,itemId);
HttpWebRequestWebRequest=(HttpWebRequest)HttpWebRequest.Create(url);
webRequest.BeginGetResponse(接收方回复,webRequest);
}
无效接收方响应回拨(IAsyncResult结果)
{
//结束调用并从响应中提取XML,然后将项添加到列表中
_items.Add(itemFromXMLResponse);
//如果此项链接到另一项,则获取该项
if(anotherItemIdExists==true)
{
FetchSingleItem(另一个项目ID);
}
其他的
_completedEvent.Set();
}
}

掌握异步编码的诀窍并不容易,尤其是当一个操作和下一个操作之间存在某种顺序依赖关系时。这正是我编写的
AsyncOperationService
要处理的问题,这是一段巧妙的短代码

首先让您读一点简单的内容:。当然,请阅读第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

     }
 }

请注意,对
Run
的调用是异步的,在获取所有项目之前,代码执行似乎会跳过它。如果用户界面对用户没有用处,甚至是危险的,那么您需要以友好的方式阻止它。最佳方式(IMO)要做到这一点,需要使用工具箱中的
BusyIndicator
控件,在调用
Run
后设置其
IsBusy
属性,并在
Run
回调中清除它。

我猜您的意思是
\u completedEvent.WaitOne()
Wait()
不存在?我已经尝试过了,它会一直等待。通过代码点击
WaitOne()
然后再也不会到达
ReceiveResponseCallBack
方法。是的。我的意思是
WaitOne()
。如果web服务器存在并且正在响应,则您所说的是不可能的。在事件开始等待之前会调用
BeginGetResponse
。操作IO是在线程池线程中执行的,不应受到等待线程的影响。您的代码中一定有其他错误。如果我删除
WaitOne()
它会立即将
\u items
返回给调用代码(调用
FetchItems
但继续在后台运行的代码(逐步执行回调方法)。如果存在
WaitOne
,它将停止所有处理并挂起。在回调的顶部设置断点。然后逐步检查代码,确保使用正确的信息调用Begin方法。我一直使用异步方法,从未遇到过您描述的问题。还要确保没有任何异常ns,这可能会阻止您报告已完成。@巴里:我不建议您使用这样的阻止方法。如果您确实觉得必须在使用
WaitOne
阻止之前使用后台工作程序将其从主UI线程中移出。您还需要一种机制来处理错误情况,以便FetchItems不会发生错误无法无限期阻止,并且在未完成时似乎未正确完成。为什么不使用WebClient.DownloadStringAsync?
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