Je reçois cet avertissement ("Fermeture capturée par implicité: ceci") de Resharper: cela signifie-t-il que ce code capture en quelque sorte l'objet englobant entier?
internal Timer Timeout = new Timer
{
Enabled = false,
AutoReset = false
};
public Task<Response> ResponseTask
{
get
{
var tcs = new TaskCompletionSource<Response>();
Timeout.Elapsed += (e, a) => tcs.SetException(new TimeoutException("Timeout at " + a.SignalTime));
if (_response != null) tcs.SetResult(_response);
else ResponseHandler += r => tcs.SetResult(_response);
return tcs.Task;
}
}
Je ne sais pas comment ni pourquoi il le fait - la seule variable qu'il devrait capturer est le TaskCompletionSource, qui est intentionnel. Est-ce réellement un problème et comment pourrais-je le résoudre s'il l'est?
EDIT: l'avertissement est sur le premier lambda (l'événement Timeout).
Il semble que le problème ne soit pas la ligne que je pense.
Le problème est que j'ai deux lambdas référençant des champs dans l'objet parent: Le compilateur génère une classe avec deux méthodes et une référence à la classe parent (this
).
Je pense que ce serait un problème car la référence à this
pourrait potentiellement rester dans l'objet TaskCompletionSource, l'empêchant d'être GCed. C'est du moins ce que j'ai trouvé sur cette question.
La classe générée ressemblerait à ceci (les noms seront évidemment différents et imprononçables):
class GeneratedClass {
Request _this;
TaskCompletionSource tcs;
public lambda1 (Object e, ElapsedEventArgs a) {
tcs.SetException(new TimeoutException("Timeout at " + a.SignalTime));
}
public lambda2 () {
tcs.SetResult(_this._response);
}
}
La raison pour laquelle le compilateur fait cela est probablement l'efficacité, je suppose, car le TaskCompletionSource
est utilisé par les deux lambdas; mais maintenant tant qu'une référence à l'un de ces lambdas est toujours référencée, la référence à l'objet Request
est également conservée.
Cependant, je ne suis pas encore prêt à trouver un moyen d'éviter ce problème.
EDIT: Je n'ai évidemment pas réfléchi à cela quand je l'ai écrit. J'ai résolu le problème en changeant la méthode comme ceci:
public Task<Response> TaskResponse
{
get
{
var tcs = new TaskCompletionSource<Response>();
Timeout.Elapsed += (e, a) => tcs.SetException(new TimeoutException("Timeout at " + a.SignalTime));
if (_response != null) tcs.SetResult(_response);
else ResponseHandler += tcs.SetResult; //The event passes an object of type Response (derp) which is then assigned to the _response field.
return tcs.Task;
}
}
Ça ressemble à _response
est un champ de votre classe.
Référencement _response
du lambda capturera this
dans la fermeture et lira this._response
lorsque le lambda s'exécute.
Pour éviter cela, vous pouvez copier _response
à une variable locale et utilisez-le à la place. Notez que cela lui fera utiliser la valeur actuelle de _response
plutôt que sa valeur éventuelle.