Résumé : J'aimerais appeler une méthode asynchrone dans un constructeur. Est-ce possible?
Détails : J'ai une méthode appelée getwritings()
qui analyse les données JSON. Tout fonctionne bien si j'appelle simplement getwritings()
dans une méthode async
et que je mets await
à gauche de celle-ci. Cependant, lorsque je crée un LongListView
dans ma page et que je tente de le remplir, je constate que getWritings()
renvoie étonnamment null
et que LongListView
est vide.
Pour résoudre ce problème, j'ai essayé de changer le type de retour de getWritings()
en Task<List<Writing>>
, puis de récupérer le résultat dans le constructeur via getWritings().Result
. Cependant, cela finit par bloquer le thread d'interface utilisateur.
public partial class Page2 : PhoneApplicationPage
{
List<Writing> writings;
public Page2()
{
InitializeComponent();
getWritings();
}
private async void getWritings()
{
string jsonData = await JsonDataManager.GetJsonAsync("1");
JObject obj = JObject.Parse(jsonData);
JArray array = (JArray)obj["posts"];
for (int i = 0; i < array.Count; i++)
{
Writing writing = new Writing();
writing.content = JsonDataManager.JsonParse(array, i, "content");
writing.date = JsonDataManager.JsonParse(array, i, "date");
writing.image = JsonDataManager.JsonParse(array, i, "url");
writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
writing.title = JsonDataManager.JsonParse(array, i, "title");
writings.Add(writing);
}
myLongList.ItemsSource = writings;
}
}
La meilleure solution consiste à reconnaître la nature asynchrone du téléchargement et à concevoir le téléchargement.
En d'autres termes, déterminez l'apparence de votre application pendant le téléchargement des données. Demandez au constructeur de la page de configurer cette vue et démarrez le téléchargement. Lorsque le téléchargement est terminé , mettez à jour la page pour afficher les données.
J'ai un article de blog sur constructeurs asynchrones que vous pouvez trouver utile. En outre, certains articles MSDN; une sur liaison de données asynchrone (si vous utilisez MVVM) et une autre sur meilleures pratiques asynchrones (c'est-à-dire, vous devez éviter async void
).
Vous pouvez aussi faire juste comme ça:
Task.Run(() => this.FunctionAsync()).Wait();
J'aimerais partager un schéma que j'ai utilisé pour résoudre ce genre de problèmes. Cela fonctionne plutôt bien je pense. Bien sûr, cela ne fonctionne que si vous avez le contrôle sur ce qui appelle le constructeur. Exemple ci-dessous
public class MyClass
{
public static async Task<MyClass> Create()
{
var myClass = new MyClass();
await myClass.Initialize();
return myClass;
}
private MyClass()
{
}
private async Task Initialize()
{
await Task.Delay(1000); // Do whatever asynchronous work you need to do
}
}
Fondamentalement, ce que nous faisons est de rendre le constructeur privé et de créer notre propre méthode asynchrone statique publique responsable de la création d'une instance de MyClass. En rendant le constructeur privé et en maintenant la méthode statique dans la même classe, nous nous sommes assurés que personne ne pourrait créer "accidentellement" une instance de cette classe sans appeler les méthodes d'initialisation appropriées. Toute la logique autour de la création de l'objet est toujours contenue dans la classe (juste dans une méthode statique).
var myClass1 = new MyClass() // Cannot be done, the constructor is private
var myClass2 = MyClass.Create() // Returns a Task that promises an instance of MyClass once it's finished
var myClass3 = await MyClass.Create() // asynchronously creates and initializes an instance of MyClass
Mis en œuvre sur le scénario actuel, il ressemblerait à quelque chose comme:
public partial class Page2 : PhoneApplicationPage
{
public static async Task<Page2> Create()
{
var page = new Page2();
await page.getWritings();
return page;
}
List<Writing> writings;
private Page2()
{
InitializeComponent();
}
private async Task getWritings()
{
string jsonData = await JsonDataManager.GetJsonAsync("1");
JObject obj = JObject.Parse(jsonData);
JArray array = (JArray)obj["posts"];
for (int i = 0; i < array.Count; i++)
{
Writing writing = new Writing();
writing.content = JsonDataManager.JsonParse(array, i, "content");
writing.date = JsonDataManager.JsonParse(array, i, "date");
writing.image = JsonDataManager.JsonParse(array, i, "url");
writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
writing.title = JsonDataManager.JsonParse(array, i, "title");
writings.Add(writing);
}
myLongList.ItemsSource = writings;
}
}
Et au lieu de faire
var page = new Page2();
Tu ferais
var page = await Page2.Create();
Essayez de remplacer ceci:
myLongList.ItemsSource = writings;
avec ça
Dispatcher.BeginInvoke(() => myLongList.ItemsSource = writings);
Pour le dire simplement, se référant à Stephen Cleary https://stackoverflow.com/a/23051370/2670
votre page sur la création doit créer des tâches dans le constructeur et vous devez les déclarer en tant que membres de la classe ou les placer dans votre groupe de tâches.
Vos données sont récupérées lors de ces tâches, mais ces tâches doivent être attendues dans le code, c’est-à-dire lors de certaines manipulations de l’UI, c.-à-d. Ok Cliquez sur etc.
J'ai développé de telles applications dans WP, nous avons eu un tas de tâches créées au début.
Vous pouvez essayer AsyncMVVM .
Page2.xaml:
<PhoneApplicationPage x:Class="Page2"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation">
<ListView ItemsSource="{Binding Writings}" />
</PhoneApplicationPage>
Page2.xaml.cs:
public partial class Page2
{
InitializeComponent();
DataContext = new ViewModel2();
}
ViewModel2.cs:
public class ViewModel2: AsyncBindableBase
{
public IEnumerable<Writing> Writings
{
get { return Property.Get(GetWritingsAsync); }
}
private async Task<IEnumerable<Writing>> GetWritingsAsync()
{
string jsonData = await JsonDataManager.GetJsonAsync("1");
JObject obj = JObject.Parse(jsonData);
JArray array = (JArray)obj["posts"];
for (int i = 0; i < array.Count; i++)
{
Writing writing = new Writing();
writing.content = JsonDataManager.JsonParse(array, i, "content");
writing.date = JsonDataManager.JsonParse(array, i, "date");
writing.image = JsonDataManager.JsonParse(array, i, "url");
writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
writing.title = JsonDataManager.JsonParse(array, i, "title");
yield return writing;
}
}
}
Un moyen rapide d'exécuter une opération fastidieuse dans n'importe quel constructeur consiste à créer une action et à l'exécuter de manière asynchrone.
new Action( async () => await getWritings())();
L'exécution de cette partie de code ne bloquera ni votre interface utilisateur ni ne vous laissera avec des threads lâches. Et si vous devez mettre à jour une interface utilisateur (en considérant que vous n'utilisez pas l'approche MVVM), vous pouvez utiliser Dispatcher pour le faire, comme beaucoup l'ont suggéré.