Question

considère ce bloc de code

public void ManageInstalledComponentsUpdate()
        {
            IUpdateView view = new UpdaterForm();
            BackgroundWorker worker = new BackgroundWorker();
            Update update = new Update();
            worker.WorkerReportsProgress = true;
            worker.WorkerSupportsCancellation = true;
            worker.DoWork += new DoWorkEventHandler(update.DoUpdate);
            worker.ProgressChanged += new ProgressChangedEventHandler(view.ProgressCallback);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(view.CompletionCallback);            
            worker.RunWorkerAsync();
            Application.Run(view as UpdaterForm);     
        }

Tout fonctionne très bien, mais je veux comprendre pourquoi les objets (ouvrier, vue et mise à jour) ne sont pas récupérés.

Était-ce utile?

La solution

Les threads comptent comme des objets racine; Je ne sais pas exactement comment BackgroundWorker fonctionne, mais il semble probable que la méthode du thread principal va accéder à l'état sur l'instance de travail; en tant que tel, le thread de travail lui-même gardera l'instance de BackgroundWorker active jusqu'à ce que (au moins) le thread se soit arrêté.

bien sûr; la collection nécessite également que tous les autres objets (en direct) aient dé-référencé l'objet worker; Notez également que la collection de variables de pile peut être différente dans debug / release et avec / sans un débogueur attaché.

[modifier] Comme il a également été noté; les gestionnaires d'événements sur le travailleur (dans votre code) conservent la " vue " et " mettre à jour " objets en vie (via le délégué), mais pas l'inverse. Tant que le travailleur a une durée de vie plus courte que la "vue" et "mise à jour", vous n'avez pas besoin d'être paranoïaque à propos de la désinscription des événements. J'ai modifié le code pour inclure un " SomeTarget " objet référencé uniquement par le travailleur: vous devriez voir cet effet (c’est-à-dire que la cible meurt avec le travailleur).

Le travailleur est-il collecté lorsque le fil meurt: voici la preuve; vous devriez voir "travailleur finalisé". après que le travailleur a signalé sa sortie:

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
class Demo : Form
{
    class ChattyWorker : BackgroundWorker
    {
        ~ChattyWorker()
        {
            Console.WriteLine("Worker finalized");
        }
    }
    class SomeTarget
    {
        ~SomeTarget()
        {
            Console.WriteLine("Target finalized");
        }
        public SomeTarget()
        {
            Console.WriteLine("Target created");
        }
        public void Foo(object sender, EventArgs args)
        {
            Console.WriteLine("Foo");
        }
    }
    static void Collect(object sender, EventArgs args)
    {
        Console.WriteLine("Collecting...");
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
    }
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
        timer.Interval = 100;
        timer.Tick += Collect;
        timer.Start();

        ChattyWorker worker = new ChattyWorker();
        worker.RunWorkerCompleted += new SomeTarget().Foo;
        worker.DoWork += delegate
        {
            Console.WriteLine("Worker starting");
            for (int i = 0; i < 10; i++)
            {
                Thread.Sleep(250);
                Console.WriteLine(i);
            }
            Console.WriteLine("Worker exiting");
        };
        worker.RunWorkerAsync();
    }
    [STAThread]
    static void Main()
    { // using a form to force a sync context
        Application.Run(new Demo());
    }
}

Autres conseils

Les gestionnaires d'événements sont des références. Par conséquent, tant que vous n'avez pas associé de gestionnaire d'événements au travailleur, celui-ci ne sera pas considéré comme "inaccessible".

Dans votre ComplitionCallback, prenez soin de décrocher les gestionnaires d'événements.

Ces objets de variable locale sont maintenus en vie jusqu'à ce que la fonction se ferme, c'est-à-dire lorsque le formulaire se ferme. Donc, annulez-les avant d’appeler Run ou déplacez-les dans un contexte différent.

public void ManageInstalledComponentsUpdate() {
    UpdaterForm form = new UpdaterForm();
    FireAndForgetWorker( form );
    Application.Run( form );  //does not return until form exits
}

void FireAndForgetWorker( IUpdateView view ) {
    BackgroundWorker worker = new BackgroundWorker();
    Update update = new Update();
    worker.WorkerReportsProgress = true;
    worker.WorkerSupportsCancellation = true;
    worker.DoWork += new DoWorkEventHandler(update.DoUpdate);
    worker.ProgressChanged += new ProgressChangedEventHandler(view.ProgressCallback);
    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(view.CompletionCallback);
    worker.RunWorkerAsync();
}

Une note à vsick:

Essayez d’exécuter le programme suivant, vous serez surpris que x vive pour toujours.

utilisant System;

class FailsOnGarbageCollection  
{ ~FailsOnGarbageCollection() { throw new NotSupportedException(); } }

class Program{
    static void WaitForever() { while (true) { var o = new object(); } }

    static void Main(string[] args)
    {
        var x = new FailsOnGarbageCollection();
        //x = null; //use this line to release x and cause the above exception
        WaitForever();
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top