Асинхронное чтение XML в Windows Phone 7 - PullRequest
1 голос
/ 30 ноября 2010

Итак, у меня есть приложение 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<TaxiCompany> taxiCoList = new List<TaxiCompany>();

            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name.Equals("pho:Title"))
                    {
                        name = reader.ReadInnerXml();
                        rName = name.Replace("&amp;","&");
                    }

                    if (reader.Name.Equals("pho:PhoneNumber"))
                    {
                        phone = reader.ReadInnerXml();
                    }

                    if (phone != "")
                    {
                        string baseURL = "http://api.search.live.net/xml.aspx?Appid=<MyAppID>&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;
        }
    }

Таким образом, этот фрагмент кода находит компанию такси и запускает асинхронную задачу для определения количества результатов поиска (gResults) для создания каждого объекта teaxicompany.

    //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.) Передайте переменные name и phone во второй метод, чтобы создать там объект такси

2.) Вернуть обратно переменную gResults и только затем создать соответствующий объект таксопредприятия?

Ответы [ 2 ]

8 голосов
/ 01 декабря 2010

Хорошо, здесь много чего нужно сделать.

Получение небольшого вспомогательного кода

Прежде всего, я хочу указать вам на пару сообщений в блоге под названием 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);

, если вы хотите получить компанию с наибольшим итогом вверху списка.

1 голос
/ 01 декабря 2010

Вы можете передать любой объект как «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's UserState должна работать независимо.

Посмотрите определение AsyncCompletedEventArgs.UserState на MSDN для получения дополнительной информации.

...