2011-03-25 118 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