J'ai un code de transfert de fichiers de haute performance que j'ai écrit en C# en utilisant l'idiome Async Programming Model (APM) (par exemple, BeginRead
/EndRead
). Ce code lit un fichier à partir d'un disque local et l'écrit dans un socket.Suggestions pour faire des E/S asynchrones avec Task Parallel Library
Pour de meilleures performances sur le matériel moderne, il est important de conserver plus d'une opération E/S en cours dans la mesure du possible. Ainsi, je publie plusieurs opérations BeginRead
sur le fichier, puis quand on termine, j'appelle un BeginSend
sur le socket, et quand cela se termine, je fais un autre BeginRead
sur le fichier. Les détails sont un peu plus compliqués que cela, mais au niveau supérieur, c'est l'idée.
J'ai le code basé sur APM qui fonctionne, mais il est très difficile à suivre et a probablement de subtils bogues de concurrence. J'adorerais utiliser TPL à la place. Je pensais Task.Factory.FromAsync
serait à peu près le faire, mais il y a un piège.
Tous les exemples d'E/S que j'ai vus (plus particulièrement la classe StreamExtensions
dans les Extras Parallel Extensions) supposent une lecture suivie d'une écriture. Cela ne fonctionnera pas comme j'ai besoin.
Je ne peux pas utiliser quelque chose de simple comme Parallel.ForEach
ou l'extension Extras Task.Factory.Iterate
parce que les tâches d'E/S async ne passe pas beaucoup de temps sur un thread de travail, donc parallèle commence juste une autre tâche, entraînant potentiellement des dizaines ou des centaines des opérations d'E/S en attente; beaucoup trop! Vous pouvez contourner cela par Wait
sur vos tâches, mais cela provoque la création d'un handle d'événement (un objet noyau) et une attente de blocage sur une poignée d'attente de tâche, qui lie un thread de travail. Ma mise en œuvre basée sur APM évite ces deux choses. J'ai joué avec différentes façons de garder plusieurs opérations de lecture/écriture en vol, et j'ai réussi à le faire en utilisant des continuations qui appellent une méthode qui crée une autre tâche, mais cela semble étrange, et certainement pas Ne sentez pas comme un TPL idiomatique.
Est-ce que quelqu'un d'autre a eu un problème avec le TPL? Aucune suggestion?
Merci pour la réponse. C'est vrai que je peux le faire, mais la façon dont 'Parallel.ForEach' est implémente c'est avec' Wait' sur les tâches en cours. J'avais espéré éviter cela parce que: 'Wait' utilise' ManualResetEventSlim', qui essaie un spinlock puis retourne à un objet Event du noyau. Création d'un noyau L'objet événement prend de nombreuses instructions et passe en mode noyau. L'attente sur un objet Event du noyau nécessite un basculement vers le mode kernel. Comme je n'avais pas à faire l'une ou l'autre de ces choses avec ma mise en œuvre de l'APM, j'espérais qu'il y aurait une manière idiomatique de TPL qui les éviterait aussi. – anelson
Lorsque vous effectuez des E/S, la dernière chose dont vous avez besoin est "de nombreuses instructions et un passage au mode noyau". Mais puisque 'EndRead' attend également un handle d'événement, je ne comprends pas votre problème. – Gabe
J'ai essayé cela dans mon propre harnais de test, et j'ai été surpris de voir à quel point la pénalité est minime pour faire ce genre d'attente. Ce n'est pas la réponse que je voulais, mais cela me semble juste, alors j'accepte cette réponse. – anelson