Come evitare la perdita di handle quando si richiama nell'interfaccia utente da System.Threading.Timer?

StackOverflow https://stackoverflow.com/questions/1603123

Domanda

Sembra richiamare Invoke su un controllo winforms in un callback da un System.Threading.Timer perde gli handle fino a quando il timer non viene eliminato. Qualcuno ha un'idea di come aggirare questo? Devo eseguire il polling per un valore ogni secondo e aggiornare l'interfaccia utente di conseguenza.

L'ho provato in un progetto di test per assicurarmi che quella fosse effettivamente la causa della perdita, che è semplicemente la seguente:

    System.Threading.Timer timer;
    public Form1()
    {
        InitializeComponent();
        timer = new System.Threading.Timer(new System.Threading.TimerCallback(DoStuff), null, 0, 500);
    }
    void DoStuff(object o)
    {
        this.Invoke(new Action(() => this.Text = "hello world"));
    }

Questo perderà 2 handle / secondo se si guarda nel task manager di Windows.

È stato utile?

Soluzione

Invoke si comporta come una coppia BeginInvoke / EndInvoke in quanto invia il messaggio al thread dell'interfaccia utente, crea un handle e attende tale handle per determinare quando il metodo Invoked è completo. È questa maniglia che "perde". Puoi vedere che si tratta di eventi senza nome usando Explorer processo per monitorare gli handle mentre l'applicazione è in esecuzione.

Se IASyncResult fosse IDisposable, lo smaltimento dell'oggetto si occuperebbe di ripulire la maniglia. Altrimenti, gli handle vengono ripuliti quando il Garbage Collector viene eseguito e chiama il finalizzatore dell'oggetto IASyncResult. Puoi vederlo aggiungendo un GC.Collect () ogni 20 chiamate a DoStuff: il conteggio degli handle diminuisce ogni 20 secondi. Naturalmente, "risolvere". il problema aggiungendo chiamate a GC.Collect () è il modo sbagliato di affrontare il problema; lascia che il garbage collector faccia il suo lavoro.

Se non è necessario la chiamata Invoke per essere sincrona, utilizzare BeginInvoke invece di Invoke e non chiamare EndInvoke; il risultato finale farà la stessa cosa ma non verrà creato alcun handle o "trapelato". "

Altri suggerimenti

C'è qualche motivo per cui non puoi usare un System.Windows.Forms.Timer qui? Se il timer è associato a quel modulo, non dovrai nemmeno invocare.

Okay, l'ho dato un po 'più di tempo e sembra che in realtà non perde maniglie, è solo la natura indeterminata del cestino della spazzatura. L'ho percorso fino a 10 ms per tick e si sarebbe arrampicato molto velocemente e 30 secondi dopo sarebbe ricaduto.

PER confermare la teoria che ho chiamato manualmente GC.Collect () su ogni callback (non farlo in progetti reali, questo era solo per testare, ci sono numerosi articoli là fuori sul perché è una cattiva idea) e il conteggio degli handle era stabile.

Interessante - Questa non è una risposta, ma in base al commento di Andrei avrei pensato che questo non avrebbe perdite maniglie allo stesso modo, tuttavia manomette la stessa velocità indicata dall'OP.

System.Threading.Timer timer;
    public Form2()
    {
        InitializeComponent();

    }

    private void UpdateFormTextCallback()
    {
        this.Text = "Hello World!";
    }

    private Action UpdateFormText;

    private void DoStuff(object value)
    {
        this.Invoke(UpdateFormText);
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        timer = new System.Threading.Timer(new TimerCallback(DoStuff), null, 0, 500);
        UpdateFormText = new Action(UpdateFormTextCallback);
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top