2011-03-25 123 views
1

假设我有下面的类:递归异步HttpWebRequests

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>给调用代码。

有人能指出我在正确的方向?我很乐意重构整个事情,如果需要的话(我怀疑是这种情况!)

+0

你为什么不使用WebClient.DownloadStringAsync? – AnthonyWJones 2011-03-25 22:14:56

回答

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的实现又使用Web客户端简化了的东西。但是,如果由于某种原因你必须坚持使用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控件,在呼叫Run后设置其IsBusy属性,并在Run回调中将其清除。

1

所有你需要的是一个线程同步thingy。我选择了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(); 
    } 
} 
+0

我认为'_completedEvent.WaitOne()'为'Wait()'不存在?我已经尝试过这一点,它只是永远等待。单步执行代码时,它会碰到WaitOne(),然后永远不会到达ReceiveResponseCallBack方法。 – codingbadger 2011-03-25 12:01:44

+0

是的。我的意思是'WaitOne()'。如果Web服务器存在并且正在响应,你所说的是不可能的。在事件开始等待之前调用'BeginGetResponse'。动作IO正在线程池线程中进行,不应等待线程的影响。你的代码中的其他东西一定是错的。 – jgauffin 2011-03-25 12:10:08

+0

如果我删除'WaitOne()'它立即返回_items到调用代码(调用'FetchItems'的代码,但是继续在后台运行(通过回调方法)如果'WaitOne'是它只是停止所有处理并挂起。 – codingbadger 2011-03-25 12:57:09