2010-04-12 4 views
7

Non eseguo molta programmazione della GUI di Windows, quindi potrebbe essere una conoscenza comune a chi ha più familiarità con WinForm di quanto lo sia io. Purtroppo non sono stato in grado di trovare risorse per spiegare il problema, che ho incontrato oggi durante il debug.Control.EndInvoke reimposta lo stack di chiamate per l'eccezione

Se chiamiamo EndInvoke su un delegato asincrono. Otterremo qualsiasi eccezione generata durante l'esecuzione del metodo re-generato. Lo stack di chiamate rifletterà la fonte originale dell'eccezione.

Tuttavia, se facciamo qualcosa di simile su un Windows.Forms.Control, l'implementazione di Control.EndInvoke reimposta lo stack di chiamate. Questo può essere osservato con un semplice test o guardando il codice in Reflector. Il brano relativo codice da EndInvoke è qui:

if (entry.exception != null) 
{ 
    throw entry.exception; 
} 

Capisco che Begin/EndInvoke per il controllo e delegati asincroni sono diversi, ma mi sarei aspettato un comportamento simile su Control.EndInvoke.

C'è qualche ragione per cui Control non fa ciò che i delegati asincroni fanno per preservare lo stack di chiamate originale?

risposta

1

Non conosco il vero motivo ma posso immaginare che i delegati asincroni siano simili a RPC mentre i delegati di controllo possono essere basati sull'invio di messaggi Win32. Diverse tecnologie quindi l'impatto di questa funzionalità potrebbe non essere lo stesso. Il delegato asincrono trarrebbe vantaggio da tutto il codice remoto, per il quale lo sviluppatore avrebbe scritto il codice per trasferire lo stack di chiamate di eccezione tra diversi processi o computer, mentre i delegati di controllo simuleranno RPC con PostMessage all'interno dello stesso processo. Squadra diversa, codice diverso.

1

Si noti inoltre che Control.EndInvoke è uno dei pochi gestiti EndInvokes nel Framework (in modo che è possibile visualizzare il codice in Reflector). Probabilmente dovrebbero avere un aiutante non gestito che getta sullo stack originale.

In realtà, penso che sia l'unico gestito EndInvoke, ma ci sono altre gestite End* routine con un parametro IAsyncResult. Non li ho controllati tutti, ma sembra che tutti quelli che ho recensito possano solo lanciare l'eccezione, o usare efficacemente la soluzione di Stephen Cleary per deviare l'uso di .NET 4 GetWaiter.GetResult, che ha alcuni shenanigans gestiti e non gestiti per cercare di ottenere lo stack ripristinato per le eccezioni.

1

non sono sicuro perché il controllo non lo fa (probabilmente solo una svista), ma si può lavorare intorno ad esso in .NET 4.0 per la pianificazione di un compito al modulo di interfaccia utente:

private BackgroundWorker bgw; 
    private TaskFactory uiTaskFactory; 

    private void Form1_Load(object sender, EventArgs e) 
    { 
     this.uiTaskFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext()); 
     this.bgw = new BackgroundWorker(); 
     this.bgw.DoWork += bgw_DoWork; 
     this.bgw.RunWorkerAsync(); 
    } 

    void bgw_DoWork(object sender, DoWorkEventArgs e) 
    { 
     var task = this.uiTaskFactory.StartNew(this.OuterTaskFunction); 
     try 
     { 
      task.Wait(); 
     } 
     catch (Exception ex) 
     { 
      // Note: Full stack trace preserved 
      MessageBox.Show(ex.InnerException.ToString()); 
     } 
    } 

    void OuterTaskFunction() 
    { 
     this.InnerTaskFunction(); 
    } 

    void InnerTaskFunction() 
    { 
     throw new InvalidOperationException("Blah."); 
    } 
-3

I non ho letto il 100% del tuo messaggio quindi non sono sicuro se questo aiuta o sto solo dicendo cose ovvie, ma quando viene rilevata un'eccezione e scrivi

"lancia iAmAnCaughtExceptionInstance;"

lo stack di chiamate non verrà salvata, si dovrebbe solo scrivere

"buttare";

e quindi lo stack di chiamate viene salvato

+1

lo so, ma dal momento che non sono colui che ha implementato Windows.Forms.Control è di scarso aiuto. –