Question Resharper: Fermeture implicitement capturée: ceci


J'obtiens cet avertissement ("Implicity -Collection signée: this") 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 suis pas sûr de savoir comment ou pourquoi cela se passe - la seule variable à capturer est la TaskCompletionSource, qui est intentionnelle. Est-ce réellement un problème et comment pourrais-je y remédier si c'est le cas?

EDIT: L'avertissement est sur le premier lambda (l'événement Timeout).


65
2017-10-18 11:48


origine


Réponses:


Il semble que le problème ne soit pas la ligne que je pense.

Le problème est que j'ai deux champs de référence lambda dans l'objet parent: Le compilateur génère une classe avec deux méthodes et une référence à la classe parente (this).

Je pense que ce serait un problème parce que la référence à this pourrait potentiellement rester dans l'objet TaskCompletionSource, l'empêchant d'être GCed. Au moins, c’est ce que suggère ce que j’ai trouvé sur cette question.

La classe générée ressemblerait à quelque chose comme ça (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, comme le TaskCompletionSource est utilisé par les deux lambda; mais maintenant, tant qu'une référence à l'un de ces lambdas est toujours référencée, la référence à Request l'objet est également maintenu.

Je ne suis pas encore plus près de savoir comment éviter ce problème, cependant.

EDIT: Je n'ai évidemment pas réfléchi à cela lorsque je l'écrivais. 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;
        }
    }

27
2017-10-18 13:47



Ça ressemble à _response est un champ dans votre classe.

Le référencement _response de la lambda va capturer this dans la fermeture, et lira this._response quand le lambda s'exécute.

Pour éviter cela, vous pouvez copier _response à une variable locale et l'utiliser à la place. Notez que cela le fera utiliser le actuel valeur de _response plutôt que sa valeur finale.


11
2017-10-18 11:49