Windows Phone 7中的异步XML读取

所以我有一个Win Phone应用程序,它正在寻找出租车公司的列表,并从Bing成功地提取他们的名字和地址,并填充正在向用户显示的列表框。 现在我要做的是,在Bing上搜索每个术语,找到每个搜索词返回的命中数并相应地对它们进行排名(一种松散的流行度排名)

void findBestResult(object sender, DownloadStringCompletedEventArgs e) { string s = e.Result; XmlReader reader = XmlReader.Create(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(s))); String name = ""; String rName = ""; String phone = ""; List taxiCoList = new List(); while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { if (reader.Name.Equals("pho:Title")) { name = reader.ReadInnerXml(); rName = name.Replace("&","&"); } if (reader.Name.Equals("pho:PhoneNumber")) { phone = reader.ReadInnerXml(); } if (phone != "") { string baseURL = "http://api.search.live.net/xml.aspx?Appid=&query=%22" + name + "%22&sources=web"; WebClient c = new WebClient(); c.DownloadStringAsync(new Uri(baseURL)); c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults); taxiCoList.Add (new TaxiCompany(rName, phone, gResults)); } phone = ""; gResults =""; } TaxiCompanyDisplayList.ItemsSource = taxiCoList; } } 

因此,该位代码找到出租车公司并启动异步任务以查找创建每个teaxicompany对象的搜索结果数(gResults)。

  //Parses search XML result to find number of results void findTotalResults(object sender, DownloadStringCompletedEventArgs e) { lock (this) { string s = e.Result; XmlReader reader = XmlReader.Create(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(s))); while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { if (reader.Name.Equals("web:Total")) { gResults = reader.ReadInnerXml(); } } } } } 

上面的剪辑找到了bing上搜索结果的数量,但问题是因为它启动异步,没有办法将第二种方法中获得的gResults与方法1中的正确公司相关联。有没有办法:

1.)将名称和电话变量传递给第二种方法,在那里创建出租车对象

2.)传回gResults变量,然后才创建相应的taxicompany对象?

好吧,这里有很多事情要做。

获取一些小帮手代码

首先,我想指出几个名为Simple Asynchronous Operation Runner Part 1和Part 2的博客文章。 我并不是建议你真正阅读它们(尽管你们也受到了欢迎,但我被告知它们并不容易阅读)。 你真正需要的是它们中的几个代码块放在你的应用程序中。

首先从第1部分复制“AsyncOperationService”框中的代码,将其放在项目中名为“AsyncOperationService.cs”的新类文件中。

其次,您需要第2部分中的“DownloadString”函数。您可以将其放在任何位置,但我建议您创建一个名为“WebClientUtils”的静态公共类并将其放在那里。

解决方案概要

我们将创建一个类( TaxiCompanyFinder ),该类具有单个方法,该方法触发异步作业以获得您所追求的结果,然后在作业完成时引发事件。

让我们开始吧。 你有一个TaxiCompany类,我将在这里创建自己的类,以便示例尽可能完整: –

 public class TaxiCompany { public string Name { get; set; } public string Phone { get; set; } public int Total { get; set; } } 

我们还需要一个EventArgs用于已完成的事件,该事件包含已完成的List以及一个Error属性,该属性将返回可能发生的任何exception。 看起来像这样: –

 public class FindCompaniesCompletedEventArgs : EventArgs { private List _results; public List Results { get { if (Error != null) throw Error; return _results; } } public Exception Error { get; private set; } public FindCompaniesCompletedEventArgs(List results) { _results = results; } public FindCompaniesCompletedEventArgs(Exception error) { Error = error; } } 

现在我们可以为TaxiCompanyFinder类开始一些简单的TaxiCompanyFinder : –

 public class TaxiCompanyFinder { protected void OnFindCompaniesCompleted(FindCompaniesCompletedEventArgs e) { Deployment.Current.Dispatcher.BeginInvoke(() => FindCompaniesCompleted(this, e)); } public event EventHandler FindCompaniesCompleted = delegate {}; public void FindCompaniesAsync() { // The real work here } } 

到目前为止,这是非常直截了当的。 您将注意到在调度程序上使用BeginInvoke ,因为将涉及一系列异步操作,我们希望确保在实际引发事件时它在UI线程上运行,从而更容易使用此类。

分离XML解析

您的原始代码存在的一个问题是它将枚举XML与尝试执行其他function混合在一起,它有点spagetti。 我识别的第一个函数是解析XML以获取名称和电话号码。 将此函数添加到类中: –

  IEnumerable CreateCompaniesFromXml(string xml) { XmlReader reader = XmlReader.Create(new StringReader(xml)); TaxiCompany result = new TaxiCompany(); while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { if (reader.Name.Equals("pho:Title")) { result.Name = reader.ReadElementContentAsString(); } if (reader.Name.Equals("pho:PhoneNumber")) { result.Phone = reader.ReadElementContentAsString(); } if (result.Phone != null) { yield return result; result = new TaxiCompany(); } } } } 

请注意,此函数从xml生成一组TaxiCompany实例,而不尝试执行任何其他操作。 还使用ReadElementContentAsString来进行更整洁的阅读。 此外,消耗xml字符串更加顺畅。

出于类似的原因,将此函数添加到类中: –

  private int GetTotalFromXml(string xml) { XmlReader reader = XmlReader.Create(new StringReader(xml)); while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { if (reader.Name.Equals("web:Total")) { return reader.ReadElementContentAsInt(); } } } return 0; } 

核心function

将以下函数添加到类中,这是执行所有真正异步工作的函数: –

  private IEnumerable FindCompanies(Uri initialUri) { var results = new List(); string baseURL = "http://api.search.live.net/xml.aspx?Appid=&query=%22{0}%22&sources=web"; string xml = null; yield return WebClientUtils.DownloadString(initialUri, (r) => xml = r); foreach(var result in CreateCompaniesFromXml(xml)) { Uri uri = new Uri(String.Format(baseURL, result.Name), UriKind.Absolute); yield return WebClientUtils.DownloadString(uri, r => result.Total = GetTotalFromXml(r)); results.Add(result); } OnFindCompaniesCompleted(new FindCompaniesCompletedEventArgs(results)); } 

它实际上看起来很直接,几乎像同步代码,这是重点。 它获取包含所需集合的初始xml,创建TaxiCompany对象集。 它通过集合添加每个的Total值。 最后,完整的活动将由全套公司解雇。

我们只需要填写FindCompaniesAsync方法: –

  public void FindCompaniesAsync() { Uri initialUri = new Uri("ConstructUriHere", UriKind.Absolute); FindCompanies(initialUri).Run((e) => { if (e != null) OnFindCompaniesCompleted(new FindCompaniesCompletedEventArgs(e)); }); } 

我不知道最初的Uri是什么,或者你是否需要以某种方式使用paramatise,但你只需要调整这个函数。 真正的魔法发生在Run扩展方法中,这将通过所有异步操作慢跑,如果有任何返回exception,则完成的事件将触发Error属性设置。

使用class级

现在你可以像这样使用这个类:

 var finder = new TaxiCompanyFinder(); finder.FindCompaniesCompleted += (s, args) => { if (args.Error == null) { TaxiCompanyDisplayList.ItemsSource = args.Results; } else { // Do something sensible with args.Error } } finder.FindCompaniesAsync(); 

您也可以考虑使用

  TaxiCompanyDisplayList.ItemsSource = args.Results.OrderByDescending(tc => tc.Total); 

如果你想让公司总排名最高的公司。

您可以将任何对象作为“UserState”传递,作为进行异步调用的一部分,然后该异步调用将在异步回调中可用。 因此,在您的第一个代码块中,更改:

 c.DownloadStringAsync(new Uri(baseURL)); c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults); 

至:

 TaxiCompany t = new TaxiCompany(rName, phone); c.DownloadStringAsync(new Uri(baseURL), t); c.DownloadStringCompleted += new DownloadStringCompletedEventHandler(findTotalResults); 

那应该允许你这样做:

 void findTotalResults(object sender, DownloadStringCompletedEventArgs e) { lock (this) { TaxiCompany t = e.UserState; string s = e.Result; ... } } 

我没有测试过这段代码,但是使用eventarg的UserState将对象传递给异步回调的一般想法应该可以正常工作。

有关详细信息,请查看MSDN上的AsyncCompletedEventArgs.UserState定义 。