Je travaille actuellement en classe avec quelques tâches asynchrones sous le capot. En fait, je dois déléguer quelques tâches à l'exécution asynchrone et être sûr que toutes sont terminées avant que la classe ne soit détruite, je pensais à attendre toutes les tâches jusqu'à la fin à l'intérieur du destructeur de classe. En recherchant les approches existantes utilisées dans la bibliothèque std du langage C++. J'étais curieux de découvrir différentes manières d'implémentation asynchrone.
Par exemple, std::thread
N'attend rien à l'intérieur du destructeur. Le code suivant échouera avec la terminaison exécutable:
void sleep() {
std::this_thread::sleep_for(std::chrono::seconds(5));
}
void execute_thead() {
auto thread = std::thread(&sleep);
}
Vous avez donc forcé d'appeler join()
pour le thread que vous avez créé. Dans l'ensemble, cela me semble plus évident, comme: "Hé! Vous avez créé une tâche asynchrone alors soyez si gentil et faites-le manuellement". Mais de toute façon, lever l'exception au cas où vous auriez oublié d'appeler join()
ou detach()
peut être inattendu;
D'un autre côté, regardons std::async
. Le code suivant attendra le résultat dans le futur destructeur:
void execute_async() {
auto result = std::async(std::launch::async, &sleep);
}
Il peut ne pas être évident que le code se bloque lorsque vous sortez du domaine, en attendant que toutes les tâches soient terminées. Il peut être difficile de trouver un goulot d'étranglement réel lors de la refactorisation ou de l'optimisation du code. Mais l'avantage est que vous n'avez pas besoin de vous soucier des tâches inachevées.
Soyez conscient, ici, je ne parle pas de stratégie basée sur les threads vs basée sur les tâches. Formellement, je demande si l'attente implicite est meilleure qu'explicite et est-ce globalement un bon moyen d'écrire du code asynchrone.
Est-ce une bonne approche d'attendre des tâches asynchrones dans le destructeur d'objets?
Personnellement, je répondrais à cette question par "Non".
La raison de cette réponse est simple: cette question ne devrait jamais se poser.
Si vous rencontrez une situation où cette question se pose, alors il y a quelque chose de mal avec votre conception de code à mon humble avis. Soit la tâche asynchrone ne doit pas exiger que l'objet reste en vie jusqu'à ce qu'il soit terminé, dans ce cas, il n'est pas nécessaire d'attendre la tâche. Ou il ne devrait jamais être possible que l'objet meure avant la fin de la tâche, auquel cas la question ne se pose pas non plus.
La principale raison pour laquelle les objets de type tâche asynchrone attendent que leurs destructeurs attendent la fin de la tâche est pas pour que quelqu'un puisse facilement écrire une tâche "asynchrone" qui se termine immédiatement. Il est là pour sécurité raisons; en fait, c'est exactement pour les mêmes raisons de sécurité que RAII doit être utilisé dans tous les autres cas.
Ces deux cas sont tout aussi mauvais:
int *ptr = new int;
do_something();
delete ptr;
---
thread th(...);
do_something();
th.join();
Ce qui se passe si do_something
émet une exception? Le thread th
sera perdu et toute connexion à celui-ci sera abandonnée au sol et oubliée. Tout comme la mémoire pointée par ptr
.
Il est raisonnable qu'un objet RAII libère sa ressource, ce qui fait qu'il n'est plus géré et doit être nettoyé manuellement. Il est beaucoup moins défendable d'en faire le comportement par défaut.
Donc, la question se résume finalement à ceci: comment sûr voulez-vous que votre code soit?