Fourniture de versions synchrones et asynchrones de la méthode en C #

J’écris une API en C # et je souhaite fournir des versions synchrones et asynchrones des méthodes publiques. Par exemple, si j’ai la fonction suivante:

public int MyFunction(int x, int y) { // do something here System.Threading.Thread.Sleep(2000); return x * y; } 

Comment puis-je créer une version asynchrone de la méthode ci-dessus (peut-être BeginMyFunction et EndMyFunction)? Existe-t-il différents moyens d’atteindre le même résultat et quels sont les avantages des différentes approches?

L’approche générique consiste à utiliser un delegate :

 IAsyncResult BeginMyFunction(AsyncCallback callback) { return BeginMyFunction(callback, null); } IAsyncResult BeginMyFunction(AsyncCallback callback, object context) { // Func is just a delegate that matches the method signature, // It could be any matching delegate and not necessarily be *generic* // This generic solution does not rely on generics ;) return new Func(MyFunction).BeginInvoke(callback, context); } int EndMyFunction(IAsyncResult result) { return new Func(MyFunction).EndInvoke(result); } 

Mehrdad Afshari répond à votre question du mieux que je pourrais suggérer. Je déconseillerais toutefois cela si possible. À moins que la seule responsabilité de votre object métier consiste à exécuter des tâches de manière synchrone ou asynchrone, vous enfreignez le principe de responsabilité unique en essayant même de le sensibiliser au fait qu’il peut s’exécuter de manière asynchrone. Il est assez facile de faire ce type d’opération dans la classe consommasortingce en utilisant des delegates anonymes:

 public void Foo(int x, int y) { ThreadPool.QueueUserWorkItem(delegate { // code to execute before running myObject.MyFunction(x, y); // code to execute after running }); } 

Si vous n’avez pas de code à exécuter avant ou après, vous pouvez utiliser un lambda pour le rendre plus concis.

 ThreadPool.QueueUserWOrkItem(() => myObject.MyFunction(x, y)); 

MODIFIER

En réponse au commentaire de @ kshahar ci-dessous, l’externalisation de l’asynchronicité est toujours une bonne idée. Il s’agit d’un problème courant qui a été résolu à l’aide de rappels depuis des décennies. Lambdas raccourcit simplement la route, et .Net 4.0 le rend encore plus simple.

 public void Foo(int x, int y) { int result = 0; // creates the result to be used later var task = Task.Factory.StartNew(() => // creates a new asynchronous task { // code to execute before running result = myObject.MyFunction(x, y); // code to execute after running }); // other code task.Wait(); // waits for the task to complete before continuing // do something with the result. } 

.Net 5 rend la tâche encore plus facile, mais je ne le connais pas suffisamment pour faire une déclaration au-delà de cela pour le moment.

Tout d’abord, si vous êtes lié au calcul, cela ne me dérangerait pas. Laissez au client le soin de déterminer s’il souhaite vous appeler de manière synchrone sur le thread actuel ou de manière asynchrone via ThreadPool.QueueUserWorkItem.

Toutefois, si votre routine contient une forme d’entrée / sortie, il peut être intéressant de fournir une version asynchrone. Assurez-vous que votre version asynchrone utilise les appels d’E / S asynchrones correspondants. Vous devrez également implémenter IAsyncResult et le renvoyer à partir de votre appel BeginMyFunction. Voir l’implémentation de Joe Duffy ici , et quelques notes sur les subtilités de différentes implémentations de BCL ici .

Vous pouvez créer une version de la méthode qui demande à un délégué de rappeler:

 delegate void PassIntDelegate (int i); delegate void PassIntIntCallbackDelegate (int i1, int i2, PassIntDelegate callback); public int MyFunction (int i1, int i2) { return i1 * i2; } public void MyFunctionAsync (int i1, int i2, PassIntDelegate callback) { new PassIntIntDelegate (_MyFunctionAsync).BeginInvoke (i1, i2, callback); } private void _MyFunctionAsync (int i1, int i2, PassIntDelegate callback) { callback.Invoke (MyFunction (i1, i2)); } 

Cette version n’est pas aussi propre que celle utilisant AsyncCallback, mais elle est un peu plus sécurisée contre les types.