Хорошо, здесь много чего нужно сделать.
Получение небольшого вспомогательного кода
Прежде всего, я хочу указать вам на пару сообщений в блоге под названием Simple Asynchronous Runner Часть 1 и Часть 2 . Я не предлагаю вам на самом деле их читать (хотя вы тоже можете, но мне сказали, что их нелегко читать). Что вам действительно нужно, так это пара блоков кода из них, которые нужно вставить в ваше приложение.
Сначала из части 1 скопируйте код из поля «AsyncOperationService», поместите его в новый файл класса в вашем проекте под названием «AsyncOperationService.cs».
Во-вторых, вам понадобится функция "DownloadString" из части 2. Вы можете поместить ее где угодно, но я рекомендую создать статический открытый класс с именем "WebClientUtils" и поместить его туда.
Схема решения
Мы собираемся создать класс (TaxiCompanyFinder
), который имеет единственный метод, который запускает асинхронное задание, чтобы получить результаты, к которым вы стремитесь, а затем имеет событие, которое вызывается, когда задание выполнено.
Итак, начнем. У вас есть TaxiCompany
класс, я здесь придумаю свой собственный, чтобы пример был как можно более полным: -
public class TaxiCompany
{
public string Name { get; set; }
public string Phone { get; set; }
public int Total { get; set; }
}
Нам также нужно EventArgs
для завершенного события, которое содержит завершенное List<TaxiCompany>
, а также свойство Error
, которое будет возвращать любое исключение, которое могло произойти. Это выглядит так: -
public class FindCompaniesCompletedEventArgs : EventArgs
{
private List<TaxiCompany> _results;
public List<TaxiCompany> Results
{
get
{
if (Error != null)
throw Error;
return _results;
}
}
public Exception Error { get; private set; }
public FindCompaniesCompletedEventArgs(List<TaxiCompany> results)
{
_results = results;
}
public FindCompaniesCompletedEventArgs(Exception error)
{
Error = error;
}
}
Теперь мы можем начать с некоторыми голыми костями для TaxiCompanyFinder
класса: -
public class TaxiCompanyFinder
{
protected void OnFindCompaniesCompleted(FindCompaniesCompletedEventArgs e)
{
Deployment.Current.Dispatcher.BeginInvoke(() => FindCompaniesCompleted(this, e));
}
public event EventHandler<FindCompaniesCompletedEventArgs> FindCompaniesCompleted = delegate {};
public void FindCompaniesAsync()
{
// The real work here
}
}
Пока все довольно просто. Вы заметите использование BeginInvoke
в диспетчере, так как будет происходить ряд асинхронных действий, и мы хотим убедиться, что когда событие действительно возникает, оно выполняется в потоке пользовательского интерфейса, что облегчает его использование. класс.
Разделительный синтаксический анализ XML
Одна из проблем вашего исходного кода заключается в том, что он смешивает перечисление XML с попытками выполнять и другие функции, все это немного спагетти. Первая функция, которую я идентифицировал, - это анализ XML для получения имени и номера телефона. Добавьте эту функцию в класс: -
IEnumerable<TaxiCompany> 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();
}
}
}
}
Обратите внимание, что эта функция возвращает набор TaxiCompany
экземпляров из xml, не пытаясь сделать что-либо еще. Также использование 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;
}
Основная функция
Добавьте в класс следующую функцию, это функция, которая выполняет всю настоящую асинхронную работу: -
private IEnumerable<AsyncOperation> FindCompanies(Uri initialUri)
{
var results = new List<TaxiCompany>();
string baseURL = "http://api.search.live.net/xml.aspx?Appid=<MyAppID>&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 или нужно ли каким-то образом его параматизировать, но вам просто нужно настроить эту функцию. Настоящая магия происходит в методе расширения Run
, который выполняет все асинхронные операции, если они возвращают исключение, то завершенное событие запускается с установленным свойством Error
.
Использование класса
Теперь вы можете использовать этот класс следующим образом:
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);
, если вы хотите получить компанию с наибольшим итогом вверху списка.