DownloadDataAsync + Delegate C # funziona nella funzione principale ma non nella classe
Domanda
Ho eseguito questo codice di persone. Ha funzionato come principale ma quando l'ho messo nella mia classe non funziona. Perché? Come utilizzare WebClient.DownloadDataAsync ( ) in questo contesto?
Si arresta in modo anomalo bc data == null e genera un'eccezione null :(.
public class Test2
{
public void func()
{
byte[] data = null;
WebClient client = new WebClient();
client.DownloadDataCompleted +=
delegate(object sender, DownloadDataCompletedEventArgs e)
{
data = e.Result;
};
Console.WriteLine("starting...");
client.DownloadDataAsync(new Uri("https://stackoverflow.com/questions/"));
while (client.IsBusy)
{
Console.WriteLine("\twaiting...");
Thread.Sleep(100);
}
Console.WriteLine("done. {0} bytes received;", data.Length);
}
}
//i tried calling on form_load and a button click
new Test2().func();
Soluzione
Questo codice ha una race condition nel campo dati. Il delegato anonimo DownloadDataCompleted viene chiamato da un thread diverso rispetto ai dati. Viene effettuata la chiamata di lunghezza e nel momento in cui DownloadDataCompleted viene chiamato IsBusy diventa falso. È una corsa tra i due thread su chi accede per primo ai dati. Se il thread principale chiama data.Length prima che i dati vengano impostati sul thread di download, si ottiene l'eccezione riferimento null. È facile vedere se si forza l'eliminazione DownloadDataCompleted di perdere sempre la corsa aggiungendo una chiamata Thread.Sleep () prima che imposti i dati.
Gli stati della discussione appariranno così:
Main Thread Download Thread client.IsBusy
Waiting.... downloading... true
leaves waiting loop calls delegate false
calls data.Length data = e.Result
Non c'è modo di sapere quale thread eseguirà prima l'ultima riga. Su una macchina con più processori, entrambi possono essere eseguiti contemporaneamente.
Dato che questo è tutto basato sul tempismo a volte funzionerà e alcune volte fallirà. È necessaria una sorta di sincronizzazione (blocco) su tutti i dati a cui accedono i thread multipli.
Altri suggerimenti
A causa del modello di threading di winform (come sottolineato da shf301
), ho modificato i codici che funzionano per me.
private void button1_Click(object sender, EventArgs e)
{
func();
}
void func()
{
WebClient client = new WebClient();
byte[] data = null;
long rcv = 0; //last number of bytes received
//check data received for progress
client.DownloadProgressChanged += delegate(object sender, DownloadProgressChangedEventArgs e)
{
if (e.BytesReceived - rcv > 1000)
{
Console.WriteLine("\tBytes Received: " + e.BytesReceived.ToString());
rcv = e.BytesReceived;
}
//else don't report
Thread.Sleep(1);
};
client.DownloadDataCompleted +=
delegate(object sender, DownloadDataCompletedEventArgs e)
{
data = e.Result;
Console.WriteLine("done. {0} bytes received;", data.Length);
};
Console.WriteLine("starting...");
//fire and forget
client.DownloadDataAsync(new Uri("http://stackoverflow.com/questions/"));
}
C'è l'output:
starting...
Bytes Received: 8192
Bytes Received: 11944
Bytes Received: 15696
Bytes Received: 20136
Bytes Received: 24232
Bytes Received: 28040
Bytes Received: 32424
Bytes Received: 36176
Bytes Received: 40616
Bytes Received: 44712
Bytes Received: 48269
done. 48269 bytes received;
Funziona per me?
C:\TEMP\ConsoleApplication5\bin\Debug>ConsoleApplication5.exe
starting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
done. 48178 bytes received;
Qual è lo scopo di utilizzare un metodo asincrono se aspetti il ??risultato in un ciclo? Usa la versione sincrona:
public class Test2
{
public void func()
{
WebClient client = new WebClient();
byte[] data = client.DownloadData(new Uri("http://stackoverflow.com/questions/"));
Console.WriteLine("done. {0} bytes received;", data.Length);
}
}
//i tried calling on form_load and a button click
new Test2().func();