Je travaille sur un projet qui doit prendre en charge les versions asynchrone et sync d'une même logique / méthode. Donc, par exemple, je dois avoir:
public class Foo
{
public bool IsIt()
{
using (var conn = new SqlConnection(DB.ConnString))
{
return conn.Query<bool>("SELECT IsIt FROM SomeTable");
}
}
public async Task<bool> IsItAsync()
{
using (var conn = new SqlConnection(DB.ConnString))
{
return await conn.QueryAsync<bool>("SELECT IsIt FROM SomeTable");
}
}
}
La logique asynchrone et sync de ces méthodes est identique à tous égards, sauf que l'une est asynchrone et l'autre non. Existe-t-il un moyen légitime d'éviter de violer le principe DRY dans ce type de scénario? J'ai vu que les gens disent que vous pouvez utiliser GetAwaiter (). GetResult () sur une méthode asynchrone et l'invoquer à partir de votre méthode de synchronisation? Ce fil est-il sûr dans tous les scénarios? Existe-t-il une autre meilleure façon de procéder ou suis-je obligé de reproduire la logique?
c#
.net
asynchronous
Marko
la source
la source
return Task.FromResult(IsIt());
)Réponses:
Vous avez posé plusieurs questions dans votre question. Je vais les décomposer légèrement différemment de vous. Mais permettez-moi d'abord de répondre directement à la question.
Nous voulons tous un appareil photo léger, de haute qualité et bon marché, mais comme le dit le proverbe, vous ne pouvez en obtenir que deux au maximum. Vous êtes dans la même situation ici. Vous voulez une solution qui soit efficace, sûre et partage du code entre les chemins synchrone et asynchrone. Vous n'en obtiendrez que deux.
Permettez-moi de vous expliquer pourquoi. Commençons par cette question:
Le point de cette question est "puis-je partager les chemins synchrones et asynchrones en faisant simplement que le chemin synchrone fasse une attente synchrone sur la version asynchrone?"
Permettez-moi d'être très clair sur ce point car il est important:
VOUS DEVEZ IMMÉDIATEMENT ARRÊTER DE PRENDRE TOUT CONSEIL DE CES PERSONNES .
C'est un très mauvais conseil. Il est très dangereux d'extraire de façon synchrone un résultat d'une tâche asynchrone, sauf si vous avez la preuve que la tâche s'est terminée normalement ou anormalement .
La raison pour laquelle ce conseil est extrêmement mauvais est, bien, envisager ce scénario. Vous voulez tondre la pelouse, mais votre lame de tondeuse à gazon est cassée. Vous décidez de suivre ce workflow:
Ce qui se produit? Vous dormez pour toujours car l'opération de vérification du courrier est désormais déclenchée par un événement qui se produit après l'arrivée du courrier .
Il est extrêmement facile d'entrer dans cette situation lorsque vous attendez de manière synchrone une tâche arbitraire. Cette tâche peut avoir un travail planifié dans le futur du thread qui attend maintenant , et maintenant ce futur n'arrivera jamais parce que vous l'attendez.
Si vous faites une attente asynchrone, tout va bien! Vous vérifiez régulièrement le courrier, et pendant que vous attendez, vous faites un sandwich ou faites vos impôts ou autre chose; vous continuez à travailler pendant que vous attendez.
N'attendez jamais de façon synchrone. Si la tâche est terminée, elle n'est pas nécessaire . Si la tâche n'est pas terminée mais planifiée pour s'exécuter à partir du thread en cours, elle est inefficace car le thread en cours pourrait servir d'autres travaux au lieu d'attendre. Si la tâche n'est pas terminée et que l'exécution est planifiée sur le thread en cours, elle est suspendue pour attendre de manière synchrone. Il n'y a aucune bonne raison d'attendre de manière synchrone, à nouveau, sauf si vous savez déjà que la tâche est terminée .
Pour plus d'informations sur ce sujet, voir
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Stephen explique le scénario du monde réel beaucoup mieux que moi.
Considérons maintenant "l'autre direction". Pouvons-nous partager du code en faisant simplement que la version asynchrone fasse la version synchrone sur un thread de travail?
C'est peut - être et probablement probablement une mauvaise idée, pour les raisons suivantes.
Il est inefficace si le fonctionnement synchrone est un travail d'E / S à latence élevée. Cela engage essentiellement un travailleur et le fait dormir jusqu'à ce qu'une tâche soit terminée. Les fils sont incroyablement chers . Ils consomment un million d'octets d'espace d'adressage minimum par défaut, ils prennent du temps, ils prennent les ressources du système d'exploitation; vous ne voulez pas graver un fil faisant un travail inutile.
L'opération synchrone peut ne pas être écrite pour être thread-safe.
C'est une technique plus raisonnable si le travail à latence élevée est liée processeur, mais si elle est alors vous ne voulez probablement pas remettre tout simplement hors d'un thread de travail. Vous souhaitez probablement utiliser la bibliothèque parallèle de tâches pour la paralléliser à autant de processeurs que possible, vous voulez probablement une logique d'annulation et vous ne pouvez pas simplement faire faire la version synchrone, car alors ce serait déjà la version asynchrone .
Lectures complémentaires; encore une fois, Stephen l'explique très clairement:
Pourquoi ne pas utiliser Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
Plus de scénarios "faire et ne pas faire" pour Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html
Qu'est-ce que cela nous laisse alors? Les deux techniques de partage de code conduisent à des blocages ou à de grandes inefficacités. La conclusion à laquelle nous arrivons est que vous devez faire un choix. Voulez-vous un programme qui est efficace et correct et qui ravit l'appelant, ou voulez-vous enregistrer quelques frappes de touches entraînées par la duplication d'une petite quantité de code entre les chemins synchrone et asynchrone? Vous n'obtenez pas les deux, j'ai peur.
la source
Task.Run(() => SynchronousMethod())
dans la version asynchrone n'est pas ce que l'OP veut?Dns.GetHostEntryAsync
est implémenté dans .NET, etFileStream.ReadAsync
pour certains types de fichiers. Le système d'exploitation ne fournit simplement aucune interface asynchrone, donc le runtime doit le simuler (et ce n'est pas spécifique au langage - par exemple, le runtime Erlang exécute une arborescence entière de processus de travail , avec plusieurs threads à l'intérieur de chacun, pour fournir des E / S de disque non bloquantes et un nom résolution).Il est difficile de donner une réponse unique à cette question. Malheureusement, il n'y a pas de moyen simple et parfait de réutiliser entre du code asynchrone et synchrone. Mais voici quelques principes à considérer:
Query()
une etQueryAsync()
l'autre), ou par établir des connexions avec des paramètres différents. Ainsi, même lorsqu'il est structurellement similaire, il existe souvent suffisamment de différences de comportement pour mériter qu'elles soient traitées comme un code distinct avec des exigences différentes. Notez les différences entre les implémentations Async et Sync des méthodes dans la classe File, par exemple: aucun effort n'est fait pour leur faire utiliser le même codeTask.FromResult(...)
.Bonne chance.
la source
Facile; demander au synchrone d'appeler celui asynchrone. Il existe même une méthode pratique
Task<T>
pour y parvenir:la source