假设我有以下课程:
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 :(得分: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
回调中清除它