2015-11-15 40 views
22

Nota: sto usando C# in Unity, questo significa che la versione di .NET 3.5 , quindi non posso utilizzare await o async parola chiave ..metodi asincroni in istruzione using

cosa accadrà a istruzione using quando Ho messo un metodo in esso che funziona in modo asincrono?

using (WebClient wc = new WebClient()) { 
    wc.DownloadFileAsync(urlUri, outputFile); 
} 
SomeMethod1(); 
SomeMethod2(); 

Come sapete, dopo che il metodo viene chiamato DownloadFileAsync(), SomeMethod1() saranno chiamati che è fuori del blocco using mentre DownloadFileAsync() è ancora lavorare. Quindi ora sono davvero confuso su cosa succederebbe all'istruzione using e al metodo asincrono in questo caso.

Dispose()wc essere chiamato al momento giusto senza problemi?

In caso negativo, come si corregge questo esempio?

+3

Questo non funzionerà. Usa 'await' con' * TaskAsync' – SLaks

+1

@JasonEvans, perché dici di si? poiché OP non sta usando 'await', allora c'è un problema. –

+0

Grazie mille per le risposte, ho fatto questi stupidi errori diverse volte .. Allora come posso evitare questo? Basta aggiungere la parola chiave await? – Jenix

risposta

21

Dai commenti:

E allora come posso evitare questo? Basta aggiungere la parola chiave await?

No, non è possibile solo farlo. (Ed è per questo che la domanda duplicata precedentemente proposta non era in realtà un duplicato e hellip: il tuo scenario è sottilmente differente.) Dovrai posticipare lo smaltimento fino al completamento del download, ma questo è complicato dalla necessità di eseguire altre due istruzioni del programma (almeno & hellip; impossibile sapere con certezza senza a good, minimal, complete code example).

Io faccio che si dovrebbe passare al metodo di awaitable WebClient.DownloadFileTaskAsync(), in quanto ciò almeno semplificare l'implementazione, rendendo semplice per mantenere la dichiarazione using.

È possibile affrontare l'altra parte del problema catturando l'tornato Task oggetto e non in attesa fino a quando dopo tuoi altre dichiarazioni del programma hanno eseguito:

using (WebClient wc = new WebClient()) { 
    Task task = wc.DownloadFileTaskAsync(urlUri, outputFile); 
    SomeMethod1(); 
    SomeMethod2(); 
    await task; 
} 

In questo modo, il download può essere avviato , gli altri due metodi chiamati e quindi il codice attenderà il completamento del download. Solo quando è completato, il blocco using verrà chiuso, consentendo l'eliminazione dell'oggetto WebClient.

Naturalmente, nella vostra attuale implementazione si sta senza dubbio gestendo un evento DownloadXXXCompleted appropriato. Se vuoi, puoi continuare a usare l'oggetto in questo modo. Ma IMHO una volta passato all'utilizzo di await, è molto meglio semplicemente mettere dopo il await il codice che deve essere eseguito al termine dell'operazione. Ciò mantiene tutto il codice relativo all'operazione in un unico posto e semplifica l'implementazione.


Se per qualche motivo non è possibile utilizzare await, allora si dovrà usare qualche meccanismo alternativo per ritardare l'Smaltire la WebClient.Alcuni approcci ti permetteranno di continuare a utilizzare using, altri richiederanno di chiamare Dispose() nel gestore di eventi DownloadXXXCompleted. Senza un esempio di codice più completo e una chiara spiegazione del motivo per cui await non è adatto, non sarebbe possibile dire con certezza quale sarebbe l'alternativa migliore.


EDIT:

Dal momento che hai confermato che non si dispone di accesso alla await nel codice attuale, qui ci sono un paio di altre opzioni compatibili con il vecchio codice & hellip;

Una possibilità è quella di aspettare proprio nello stesso filo dopo l'avvio dell'operazione:

using (WebClient wc = new WebClient()) { 
    object waitObject = new object(); 
    lock (waitObject) 
    { 
     wc.DownloadFileCompleted += (sender, e) => 
     { 
      lock (waitObject) Monitor.Pulse(waitObject); 
     }; 
     wc.DownloadFileAsync(urlUri, outputFile); 
     SomeMethod1(); 
     SomeMethod2(); 
     Monitor.Wait(waitObject); 
    } 
} 

(Nota: si può utilizzare qualsiasi sincronizzazione idoneo soprastante, come ManualResetEvent, CountdownEvent, o anche Semaphore e/o equivalenti "slim" Io uso Monitor semplicemente per la sua semplicità ed efficienza, e prendo come i lettori possono regolare per accogliere i loro mezzi preferiti di sincronizzazione.Un'eventuale ragione si potrebbe preferire qualcosa altro di Monitor è che gli altri tipi di sincronizzazione techni le domande non corrono il rischio di bloccare lo stesso gestore di eventi DownloadFileCompleted in attesa dei metodi SomeMethod1() e SomeMethod2() da completare. Indipendentemente dal fatto che ciò sia importante dipende ovviamente dal tempo di utilizzo di tali metodi rispetto al download del file.)

Quanto sopra, tuttavia, bloccherà il thread corrente. In alcuni casi, ciò può essere corretto, ma molto spesso l'operazione viene avviata nel thread dell'interfaccia utente e tale thread non deve essere bloccato per la durata dell'operazione. In questo caso, si vuole rinunciare using del tutto e basta chiamare Dispose() dal gestore di eventi di completamento:

WebClient wc = new WebClient(); 
wc.DownloadFileCompleted += (sender, e) => 
{ 
    wc.Dispose(); 
}; 
wc.DownloadFileAsync(urlUri, outputFile); 
SomeMethod1(); 
SomeMethod2(); 
+0

Ho appena scoperto che la parola chiave await non può essere utilizzata nella versione 3.5 che Unity3D sta usando .. Ma la tua risposta è molto utile! Grazie mille! – Jenix

+1

Molto buono. Soprattutto l'ultimo è molto facile e pulito! :) – Jenix

+2

'Monitor.Pulse()' non mantiene lo "stato a impulsi", quindi in questo modo si corre il rischio di deadlocking se la richiamata viene completata prima che il thread principale sia in attesa (improbabile, ma possibile). Un 'Semaphore' potrebbe essere migliore in questa situazione, o il tempismo sull'attesa. –

6

Il System.Net.WebClient fornisce l'evento DownloadFileCompleted. È possibile aggiungere un gestore per quell'evento e smaltire il client in quel momento.

+0

Sì! Questo è uno dei modi migliori che posso scegliere, grazie! – Jenix