269 votes

Comment appeler une méthode asynchrone depuis un getter ou un setter ?

Quelle serait la manière la plus élégante d'appeler une méthode asynchrone à partir d'un getter ou d'un setter en C# ?

Voici un pseudo-code pour m'aider à m'expliquer.

async Task<IEnumerable> MyAsyncMethod()
{
    return await DoSomethingAsync();
}

public IEnumerable MyList
{
    get
    {
         //call MyAsyncMethod() here
    }
}

4 votes

Ma question serait de savoir pourquoi. Une propriété est censée imiter quelque chose comme un champ en ce sens qu'elle doit généralement effectuer peu de travail (ou du moins très rapidement). Si vous avez une propriété de longue durée, il vaut mieux l'écrire comme une méthode pour que l'appelant sache qu'il s'agit d'un travail plus complexe.

0 votes

@James : C'est exactement ça - et je soupçonne que c'est la raison pour laquelle cela n'a pas été explicitement pris en charge dans le CTP. Ceci étant dit, vous pouvez toujours rendre la propriété de type Task<T> qui retournera immédiatement, aura une sémantique de propriété normale, et permettra toujours de traiter les choses de manière asynchrone si nécessaire.

19 votes

James Mon besoin découle de l'utilisation de Mvvm et de Silverlight. Je veux pouvoir me lier à une propriété, où le chargement des données se fait paresseusement. La classe d'extension ComboBox que j'utilise exige que la liaison se fasse à l'étape InitializeComponent(), mais le chargement réel des données se fait beaucoup plus tard. En essayant d'accomplir avec aussi peu de code que possible, getter et async semble être la combinaison parfaite.

259voto

Stephen Cleary Points 91731

Il n'y a pas technique raison pour laquelle async ne sont pas autorisées en C#. Il s'agit d'une décision de conception délibérée, car les "propriétés asynchrones" sont un oxymore.

Les propriétés doivent renvoyer les valeurs actuelles ; elles ne doivent pas déclencher d'opérations en arrière-plan.

En général, lorsque quelqu'un veut une "propriété asynchrone", ce qu'il veut vraiment, c'est une de ces propriétés :

  1. Une méthode asynchrone qui renvoie une valeur. Dans ce cas, changez la propriété en une async méthode.
  2. Une valeur qui peut être utilisée dans la liaison de données mais qui doit être calculée/récupérée de manière asynchrone. Dans ce cas, il faut soit utiliser un async pour l'objet contenant ou utiliser une méthode d'usine async InitAsync() méthode. La valeur liée aux données sera default(T) jusqu'à ce que la valeur soit calculée/récupérée.
  3. Une valeur dont la création est coûteuse, mais qui doit être mise en cache pour une utilisation ultérieure. Dans ce cas, utilisez AsyncLazy de mon blog o Bibliothèque AsyncEx . Cela vous donnera un await de la propriété.

Mise à jour : Je couvre propriétés asynchrones dans l'un de mes récents billets de blog "async OOP".

1 votes

Au point 2, vous ne tenez pas compte du scénario habituel dans lequel l'établissement de la propriété doit être effectué. à nouveau initialiser les données sous-jacentes (pas seulement dans le constructeur). Existe-t-il un autre moyen que d'utiliser Nito AsyncEx ou en utilisant Dispatcher.CurrentDispatcher.Invoke(new Action(..) ?

0 votes

@Gerard : Je ne vois pas pourquoi le point (2) ne fonctionnerait pas dans ce cas. Il suffit d'implémenter INotifyPropertyChanged et décidez ensuite si vous voulez que l'ancienne valeur soit retournée ou default(T) pendant que la mise à jour asynchrone est en cours.

1 votes

@Stephan : ok, mais quand j'appelle la méthode async dans le setter, j'obtiens l'avertissement CS4014 "not awaited" (ou est-ce seulement dans Framework 4.0 ?). Est-ce que vous conseillez de supprimer cet avertissement dans un tel cas ?

111voto

Reed Copsey Points 315315

Vous ne pouvez pas l'appeler de manière asynchrone, car il n'y a pas de support pour les propriétés asynchrones, seulement pour les méthodes asynchrones. En tant que tel, il existe deux options, toutes deux tirant parti du fait que les méthodes asynchrones dans la PTC sont en réalité simplement une méthode qui renvoie Task<T> o Task :

// Make the property return a Task<T>
public Task<IEnumerable> MyList
{
    get
    {
         // Just call the method
         return MyAsyncMethod();
    }
}

Ou :

// Make the property blocking
public IEnumerable MyList
{
    get
    {
         // Block via .Result
         return MyAsyncMethod().Result;
    }
}

2 votes

Merci pour votre réponse. Option A : Renvoyer une tâche n'est pas vraiment utile à des fins de liaison. Option B : .Result, comme vous le mentionnez, bloque le thread de l'interface utilisateur (dans Silverlight), ce qui nécessite l'exécution de l'opération sur un thread d'arrière-plan. Je vais voir si je peux trouver une solution viable avec cette idée.

3 votes

@duluca : Vous pouvez aussi essayer d'avoir une méthode qui est comme private async void SetupList() { MyList = await MyAsyncMethod(); } De cette façon, MyList serait définie (et ensuite automatiquement liée, si elle implémente INPC) dès que l'opération asynchrone est terminée...

0 votes

La propriété doit se trouver dans un objet que j'ai déclaré comme ressource de page, donc j'avais vraiment besoin que cet appel provienne du getter. Veuillez consulter ma réponse pour connaître la solution que j'ai trouvée.

57voto

Doguhan Uluca Points 1733

J'avais vraiment besoin que l'appel provienne de la méthode get, en raison de mon architecture découplée. J'ai donc créé l'implémentation suivante.

Uso: Titre est dans un ViewModel ou un objet que vous pourriez déclarer statiquement comme ressource de page. Liez-le et la valeur sera remplie sans bloquer l'interface utilisateur, lorsque getTitle() revient.

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

9 votes

mise à jour du 18/07/2012 dans Win8 RP nous devons changer l'appel du Dispatcher en : Window.Current.CoreWindow.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, async () => { Title= await GetTytleAsync(url);}) ;

0 votes

La méthode lambda ne devrait-elle pas être () => { _Title = await getTitle(); } ?

7 votes

@ChristopherStevenson, je le pensais aussi, mais je ne crois pas que ce soit le cas. Parce que le getter est exécuté comme un feu et oublie, sans appeler le setter à la fin, la liaison ne sera pas mise à jour lorsque le getter a terminé son exécution.

13voto

MohsenB Points 339

Vous pouvez utiliser Task comme ceci :

public int SelectedTab
        {
            get => selected_tab;
            set
            {
                selected_tab = value;

                new Task(async () =>
                {
                    await newTab.ScaleTo(0.8);
                }).Start();
            }
        }

11voto

Je pense que nous pouvons attendre la valeur en retournant d'abord null et ensuite obtenir la vraie valeur, donc dans le cas de Pure MVVM (projet PCL par exemple) je pense que la solution suivante est la plus élégante :

private IEnumerable myList;
public IEnumerable MyList
{
  get
    { 
      if(myList == null)
         InitializeMyList();
      return myList;
     }
  set
     {
        myList = value;
        NotifyPropertyChanged();
     }
}

private async void InitializeMyList()
{
   MyList = await AzureService.GetMyList();
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X