Comment initialiser un object en utilisant un modèle async-wait

J’essaie de suivre le modèle RAII dans mes classes de service, ce qui signifie que lorsqu’un object est construit, il est complètement initialisé. Cependant, je suis confronté à des difficultés avec les API asynchrones. La structure de la classe en question ressemble à suivre

class ServiceProvider : IServiceProvider // Is only used through this interface { public int ImportantValue { get; set; } public event EventHandler ImportantValueUpdated; public ServiceProvider(IDependency1 dep1, IDependency2 dep2) { // IDependency1 provide an input value to calculate ImportantValue // IDependency2 provide an async algorithm to calculate ImportantValue } } 

Je vise également à éliminer les effets secondaires de l’ ImportantValue getValueValue et à le rendre thread-safe.

Désormais, les utilisateurs de ServiceProvider vont en créer une instance, s’abonner à un événement du changement ImportantValue et obtenir la valeur initiale ImportantValue . Et voici le problème, avec la valeur initiale. Etant donné que la valeur ImportantValue est calculée de manière asynchrone, la classe ne peut pas être entièrement initialisée dans le constructeur. Il peut être correct d’avoir cette valeur comme nulle au départ, mais il me faut ensuite un endroit où elle sera calculée pour la première fois. Un endroit naturel pour cela pourrait être le getter de ImportantValue , mais je cherche à le rendre fil-safe et sans effets secondaires.

Donc, je suis fondamentalement coincé avec ces contradictions. Pourriez-vous s’il vous plaît m’aider et offrir une alternative? Avoir une valeur initialisée dans constructeur alors que sympa n’est pas vraiment nécessaire, mais aucun effet secondaire et thread-safety de la propriété n’est obligatoire.

Merci d’avance.

EDIT: Une dernière chose à append. J’utilise Ninject pour l’instanciation et, autant que je sache, il ne prend pas en charge les méthodes asynchrones pour créer une liaison. Bien que l’approche avec le lancement d’une opération basée sur les tâches dans le constructeur fonctionne, je ne peux pas attendre le résultat.

C’est-à-dire que deux prochaines approches (proposées comme réponses jusqu’à présent) ne seront pas compilées, car Task est renvoyé et non mon object:

 Kernel.Bind().ToMethod(async ctx => await ServiceProvider.CreateAsync()) 

ou

 Kernel.Bind().ToMethod(async ctx => { var sp = new ServiceProvider(); await sp.InitializeAsync(); }) 

La liaison simple fonctionnera, mais je n’attends pas le résultat de l’initialisation asynchrone démarrée dans le constructeur, comme proposé par Stephen Cleary:

 Kernel.Bind().To(); 

… et ça ne me va pas.

J’ai un article de blog qui décrit plusieurs approches de la construction async .

Je recommande la méthode d’usine asynchrone telle que décrite par Reed, mais cela n’est parfois pas possible (par exemple, l’dependency injection). Dans ces cas, vous pouvez utiliser un modèle d’initialisation asynchrone comme ceci:

 public sealed class MyType { public MyType() { Initialization = InitializeAsync(); } public Task Initialization { get; private set; } private async Task InitializeAsync() { // Asynchronously initialize this instance. await Task.Delay(100); } } 

Vous pouvez ensuite construire le type normalement, mais gardez à l’esprit que la construction ne lance que l’initialisation asynchrone. Lorsque vous avez besoin que le type soit initialisé, votre code peut faire:

 await myTypeInstance.Initialization; 

Notez que si l’ Initialization est déjà terminée, l’exécution (de manière synchrone) se poursuit après l’ await .


Si vous voulez une propriété asynchrone réelle , j’ai aussi un article de blog pour ça. AsyncLazy peut AsyncLazy bénéfique pour votre situation:

 public sealed class MyClass { public MyClass() { MyProperty = new AsyncLazy(async () => { await Task.Delay(100); return 13; }); } public AsyncLazy MyProperty { get; private set; } } 

Une option potentielle serait de déplacer ceci vers une méthode factory au lieu d’utiliser un constructeur.

Votre méthode d’usine pourrait alors renvoyer une Task , ce qui vous permettrait d’effectuer l’initialisation de manière asynchrone, mais ne renverra pas le ServiceProvider construit jusqu’à ce que ImportantValue ait été calculée (de manière asynchrone).

Cela permettrait à vos utilisateurs d’écrire du code comme:

 var sp = await ServiceProvider.CreateAsync(); int iv = sp.ImportantValue; // Will be initialized at this point 

Vous pouvez utiliser mon conteneur AsyncContainer IoC qui prend en charge le même scénario que vous.

Il prend également en charge d’autres scénarios pratiques tels que les initialiseurs asynchrones, les fabriques conditionnelles au moment de l’exécution, dépendent des fonctions de fabrique asynchrone et sync.

 //The email service factory is an async method public static async Task EmailServiceFactory() { await Task.Delay(1000); return new EmailService(); } class Service { //Constructor dependencies will be solved asynchronously: public Service(IEmailService email) { } } var container = new Container(); //Register an async factory: container.Register(EmailServiceFactory); //Asynchronous GetInstance: var service = await container.GetInstanceAsync(); //Safe synchronous, will fail if the solving path is not fully synchronous: var service = container.GetInstance(); 

Il s’agit d’une légère modification du motif @StephenCleary d’initialisation asynchrone.

La différence étant que l’appelant n’a pas besoin de «se souvenir» d’ await la InitializationTask , ni même de rien connaître de la initializationTask (en fait, elle est maintenant passée à privée).

La façon dont cela fonctionne est que dans chaque méthode qui utilise les données initialisées, il existe un appel initial pour await _initializationTask . Cela retourne instantanément la deuxième fois – parce que l’object _initializationTask aura lui-même un ensemble booléen ( IsCompleted que le mécanisme ” IsCompleted ” vérifie) – ne vous inquiétez donc pas s’il initialise plusieurs fois.

Le seul problème que je connaisse est que vous ne devez pas oublier de l’appeler dans chaque méthode qui utilise les données.

 public sealed class MyType { public MyType() { _initializationTask = InitializeAsync(); } private Task _initializationTask; private async Task InitializeAsync() { // Asynchronously initialize this instance. _customers = await LoadCustomersAsync(); } public async Task LookupCustomer(ssortingng name) { // Waits to ensure the class has been initialized properly // The task will only ever run once, sortingggered initially by the constructor // If the task failed this will raise an exception // Note: there are no () since this is not a method call await _initializationTask; return _customers[name]; } // one way of clearing the cache public void ClearCache() { InitializeAsync(); } // another approach to clearing the cache, will wait until complete // I don't really see a benefit to this method since any call using the // data (like LookupCustomer) will await the initialization anyway public async Task ClearCache2() { await InitializeAsync(); } } 

Je sais que c’est une vieille question, mais c’est la première qui apparaît sur Google et, très franchement, la réponse acceptée est une mauvaise réponse. Vous ne devez jamais forcer un délai pour pouvoir utiliser l’opérateur d’attente.

Une meilleure approche d’une méthode d’initialisation:

 private async Task InitializeAsync() { try{ // Initialize this instance. } catch{ // Handle issues return await Task.FromResult(false); } return await Task.FromResult(true); } 

Ceci utilisera le framework async pour initialiser votre object, mais retournera une valeur booléenne.

Pourquoi est-ce une meilleure approche? Tout d’abord, vous n’imposez pas un retard dans votre code, ce qui, à mon humble avis, fait totalement échec à l’utilisation du framework async. Deuxièmement, il est judicieux de renvoyer quelque chose d’une méthode asynchrone. De cette façon, vous savez si votre méthode asynchrone a réellement fonctionné / a fait ce qu’elle était supposée faire. Renvoyer uniquement Task équivaut à renvoyer null sur une méthode non asynchrone.