Eh bien, il y a beaucoup à faire ici.
Obtenir un petit code aide
Tout d'abord, je veux vous indiquer quelques messages de blog appelé Simple Asynchronous Operation Runner Part 1 et Part 2. Je ne suggère pas que vous les lisiez réellement (bien que vous soyez les bienvenus aussi mais on m'a dit que ce n'était pas facile à lire). Ce dont vous avez besoin, c'est de quelques blocs de code pour les mettre dans votre application.
D'abord à partir de la partie 1, copiez le code depuis la boîte "AsyncOperationService", placez-le dans un nouveau fichier de classe dans votre projet appelé "AsyncOperationService.cs".
Ensuite, vous aurez besoin de la fonction "DownloadString" de la partie 2. Vous pouvez mettre cela n'importe où mais je vous recommande de créer une classe publique statique appelée "WebClientUtils" et de le mettre là.
Aperçu de la solution
Nous allons créer une classe (TaxiCompanyFinder
) qui a une seule méthode qui se déclenche en dehors du travail asynchrone pour obtenir les résultats que vous êtes après et a alors un événement qui est soulevée quand le travail est terminé.
Alors commençons. Vous avez une classe TaxiCompany
, je vais inventer ma propre ici afin que l'exemple est aussi complète que possible: -
public class TaxiCompany
{
public string Name { get; set; }
public string Phone { get; set; }
public int Total { get; set; }
}
Nous avons également besoin d'un EventArgs
pour l'événement terminé qui porte le List<TaxiCompany>
complété et également une propriété Error
cela renverra toute exception qui pourrait avoir eu lieu. Cela ressemble à ceci: -
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;
}
}
Maintenant, nous pouvons prendre un nouveau départ avec quelques os nus pour la TaxiCompanyFinder
classe: -
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
}
}
C'est assez en avant jusqu'à présent droite. Vous remarquerez l'utilisation de BeginInvoke
sur le répartiteur, puisqu'il y aura une série d'actions asynchrones impliquées, nous voulons nous assurer que lorsque l'événement est réellement levé, il s'exécute sur le thread UI, ce qui facilite la consommation de cette classe.
analyse XML séparation
L'un des problèmes de votre code d'origine a est qu'il mélange XML avec l'énumération essayant de faire d'autres fonctions, ses tous un spagetti bits. La première fonction que j'ai identifiée est l'analyse du code XML pour obtenir le nom et le numéro de téléphone. Ajouter cette fonction à la classe: -
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();
}
}
}
}
Notez que cette fonction donne un ensemble de TaxiCompany
instances du xml sans essayer de faire quoi que ce soit d'autre. Également l'utilisation de ReadElementContentAsString
qui fait pour la lecture plus propre. En outre, la consommation de la chaîne xml est beaucoup plus lisse.
Pour des raisons similaires ajouter cette fonction à la classe: -
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;
}
La fonction principale
Ajoutez la fonction suivante à la classe, c'est la fonction qui fait tout le travail réel async :
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));
}
Il semble réellement assez simple, presque comme le code synchonous qui est le point. Il récupère le fichier XML initial contenant l'ensemble dont vous avez besoin, crée l'ensemble des objets TaxiCompany
. Il foreaches à travers l'ensemble en ajoutant la valeur Total
de chacun. Enfin, l'événement terminé est renvoyé avec l'ensemble des sociétés.
Nous avons juste besoin de remplir la méthode FindCompaniesAsync
: -
public void FindCompaniesAsync()
{
Uri initialUri = new Uri("ConstructUriHere", UriKind.Absolute);
FindCompanies(initialUri).Run((e) =>
{
if (e != null)
OnFindCompaniesCompleted(new FindCompaniesCompletedEventArgs(e));
});
}
Je ne sais pas ce que le premier Uri est ou si vous devez paramatise d'une certaine façon, mais vous auriez juste besoin de modifier cette fonction .La vraie magie se produit dans la méthode d'extension Run
, cela parcourt toutes les opérations asynchrones, s'il y en a, renvoyez une exception, puis l'événement terminé se déclenche avec l'ensemble de propriétés Error
.
Utilisation de la classe
maintenant en vous pouvez consommer cette classe comme ceci:
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();
Vous pourriez également envisager d'utiliser
TaxiCompanyDisplayList.ItemsSource = args.Results.OrderByDescending(tc => tc.Total);
si vous voulez obtenir la compagnie le total le plus élevé en haut de la liste.
merci, UserState est exactement ce que je cherchais – varunsrin