7
votes

Contrat de contrat lors de la mise en œuvre d'une méthode qui renvoie une tâche

existe-t-il une "meilleure pratique" MS ou un contrat de contrat lors de la mise en œuvre d'une méthode qui renvoie une tâche vis-à-vis des exceptions? Ceci est arrivé lors de la rédaction des tests d'unité et j'essayais de comprendre si je devais tester / gérer cette condition (je reconnais que la réponse pourrait être "codage défensive", mais je ne veux pas que ce soit la réponse). Ie p>

  1. La méthode doit toujours renvoyer une tâche, qui devrait contenir l'exception lancée. P> li>

  2. Méthode doit toujours renvoyer une tâche, sauf lorsque la méthode fournit des arguments non valides (c'est-à-dire argumentException). P> li>

  3. La méthode doit toujours renvoyer une tâche, sauf lorsque le développeur va voyou et fait ce qu'il veut (JK). P> Li> OL>

    Task Foo1Async(string id){
      if(id == null){
        throw new ArgumentNullException();
       }
    
      // do stuff
    }
    
    Task Foo2Async(string id){
      if(id == null){
        var source = new TaskCompletionSource<bool>();
        source.SetException(new ArgumentNullException());
        return source.Task;
      }
    
      // do stuff
    }
    
    Task Bar(string id){
      // argument checking
      if(id == null) throw new ArgumentNullException("id")    
    
      try{
        return this.SomeService.GetAsync(id).ContinueWith(t => {
           // checking for Fault state here
           // pass exception through.
        })
      }catch(Exception ex){
        // handling more Fault state here.
        // defensive code.
        // return Task with Exception.
        var source = new TaskCompletionSource<bool>();
        source.SetException(ex);
        return source.Task;
      }
    }
    


3 commentaires

Question interessante. Je n'ai jamais vu une telle directive, alors je serais tenté de dire 4: la méthode peut adresser une exception ou renvoyer une tâche contenant cette exception, et l'appelant doit traiter ces cas équivalents.


Le codage défensif @HVD suggérerait que l'appelant manipule pour les deux cas, mais le minimaliste en moi ne veut pas gérer des exceptions exprimées de plusieurs manières. L'exception serait une vérification des arguments (en train de me contredire).


Dans le même temps, cependant, la manière dont une exception est lancée ne devrait pas dépendre de la mise en œuvre de la méthode à l'aide de async , car c'est un détail de mise en œuvre non visible à l'appelant. Si vous êtes d'accord avec cela, alors les deux cas doivent être traités comme équivalents, sinon vous ne devriez jamais résoudre de manière synchrone.


3 Réponses :


3
votes

Le cas général lorsque les méthodes de retour des méthodes sont parce qu'elles sont des méthodes asynchrones. Dans ces cas, il est courant qu'une exception dans la partie synchrone de la méthode doit être lancée, tout comme dans une autre méthode, et dans la partie asynchrone doit être stockée dans la tâche renvoyée (automatiquement en appelant un < code> async méthode ou délégué anonyme).

Donc, dans les cas simples, tels que des paramètres non valides, lancez simplement une exception comme dans FOO1ASYNC . Dans l'affaire plus complexe concernant l'opération asynchrone définit une exception sur la tâche renvoyée comme dans FOO2ASYNC

Cette réponse suppose que vous parlez de Task Les méthodes de retour qui ne sont pas marquées avec async . Dans ceux qui ne sont pas contrôlés sur la tâche créée et que toute exception serait automatiquement stockée dans cette tâche (la question serait donc non pertinente).


8 commentaires

+1. D'accord. Des trucs comme la validation des paramètres n'ont aucune raison d'attendre. Si vous ne pouvez pas jeter immédiatement, mettez-le dans la tâche.


J'ai mis à jour l'exemple avec la barre (chaîne): tâche. La barre a une dépendance sur certains services qui renvoie une tâche. L'exemple doit exception / état de défaut est traité dans 2 zones (essayez de bloquer et de continuer.).


@Kevin oui. C'est à peu près ce que je voulais dire


@ l3arnon: Dans ces cas, il est courant qu'une exception dans la partie synchrone de la méthode doit être lancée comme dans une autre méthode, et dans la partie asynchrone doit être stockée à l'intérieur de la tâche renvoyée. peu importe Si vous lancez de la partie synchrone ou asynchrone de async méthode. Dans les deux cas, l'exception sera stockée dans la tâche . La seule différence est que l'objet Task sera terminé instantanément (défaut) dans le premier cas. Si async la méthode jette de la partie synchrone, mais l'appelant ne doit pas attendre la tâche renvoyée, l'exception ne sera pas retirée.


@Noseratio Les méthodes de l'exemple sont asynchrones, mais non marquées avec async qui est assez courante lorsque vous supprimez le modificateur async async où il est inutile.


@Kevin et voici les pensées de Jon Skeet à ce sujet: "Nous sommes dans une situation similaire ici, si nous voulons que des arguments soient validés avec impatience, ce qui a provoqué une exception directement à l'appelant" Comment pouvons-nous valider les arguments?


@ L3arnon, mon point est que l'appelant ne doit pas faire de supposer si la méthode a async signature. Je ne pense pas que ce soit une bonne idée d'utiliser TaskCompletSource pour propager des exceptions comme @kevin fait à l'intérieur de la barre , je ferais simplement renvoyer cela.someservice.getaSync (id) .


@Noseratio Il ne s'agit pas de l'appelant, il s'agit de savoir comment mettre en œuvre la méthode elle-même. À propos de bar , retourner someervice.getaSync (id) est exactement ce que j'ai dit. L'exception de synchronisation est lancée et l'exception ASYNC est dans la tâche



6
votes

J'ai récemment posé une question quelque peu similaire:

Gestion des exceptions de la partie synchrone de la méthode async . P>

Si la méthode comporte un async code> Signature, peu importe si vous jetez du synchrones ou partie asynchrone de la méthode. Dans les deux cas, l'exception sera stockée à l'intérieur de la tâche code> code>. La seule différence est que la tâche résultante code> sera terminée instantanément (défectueuse) dans l'ancien cas. P>

si la méthode n'a pas async code> Signature, l'exception peut être projetée sur le cadre de pile de l'appelant. P>

imo, dans les deux cas, l'appelant ne doit pas faire d'hypothèse forte> sur la question de savoir si l'exception a été lancée à partir de la synchrone ou de l'exception. partie asynchrone, ou si la méthode a async code> signature, du tout. p>

Si vous avez vraiment besoin de savoir si la tâche est terminée de manière synchrone, vous pouvez toujours vérifier sa tâche .Commété code> / défaut code> / annulé code> Statut ou Taste.Exception code> propriété, sans attente: p>

Task Foo1Async(string id){
  if(id == null) {
    throw new ArgumentNullException();
   }

  // do stuff
}

Task Foo2Async(string id) {
  if(id == null){
    throw new ArgumentNullException();
   }

  // do stuff
}

Task Bar(string id) {
  // argument checking
  if(id == null) throw new ArgumentNullException("id")    
  return this.SomeService.GetAsync(id);
}


0 commentaires

5
votes

Je sais que Jon Skeet est un fan de vérification de style de préconformation dans une méthode synchrone distincte de sorte qu'elles soient jetées directement.

Cependant, ma propre opinion est "ça n'a pas d'importance". Considérez Eric Lippert's taxonomie d'exception . Nous convenons tous que les exceptions exogènes doivent être placées sur la tâche renvoyée (non jetée directement sur le cadre de la pile de l'appelant). Les exceptions vexées doivent être complètement évitées. Les seuls types d'exceptions en question sont des exceptions osseuses (par exemple, des exceptions d'arguments).

Mon argument est que peu importe comment ils sont jetés parce que vous ne devriez pas écrire de code de production qui les attrape . Les tests de votre unité sont le seul code qui devrait attraper argumentexception et amis, et si vous utilisez attendre alors cela n'a pas d'importance quand ils sont lancés.


6 commentaires

Dans le cas de l'OP, l'exception éventuelle éventuelle possible de la barre par par this.someservice.getaSync () est considéré comme exogène (je le pense)? Devrait-il être ensuite enveloppé avec tcs.setexception , la façon dont il est fait là-bas?


Oui, c'est exogène. Je voudrais implémenter bar avec async / attendre , qui utiliserait tcs.setexception sous les couvercles.


J'apprécierais que si vous pouviez montrer comment bar regarderait avec taskcompletsource et async / attendre . J'ai envie de me manquer quelque chose d'évident. Merci!


L'approche TCS est ce que l'OP a écrit dans sa question. Le async / attendre serait si (id == null) lancer une nouvelle argumentNullexception ("ID"); Attendre cela.someservice.getaSync (ID); (Ignoring the "Retourner simplement l'optimisation de la tâche getasync tâche").


Ah, donc c'est tcs ou async / attendre comme ce Pastebin. com / ncxh3t9x ?


@Noeratio: Oui, c'est ce que je voulais dire.