DownloadDataAsync + Delegate C # fonctionne dans la fonction main mais pas dans la classe
Question
J'ai exécuté ce code de personne. Cela a fonctionné en tant que principal mais quand je l'ai mis dans ma classe cela ne fonctionne pas. Pourquoi? Comment utiliser WebClient.DownloadDataAsync ( ) méthode dans ce contexte?
Il bloque bc data == null et lève une exception 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();
La solution
Ce code a une condition de concurrence dans le champ de données. Le délégué anonyme DownloadDataCompleted est appelé à partir d'un thread différent de celui de l'appel data.Length et, au moment où DownloadDataCompleted est appelé, IsBusy devient false. C'est une course entre les deux threads sur qui accède en premier aux données. Si le thread principal appelle data.Length avant que les données ne soient définies sur le thread de téléchargement, vous obtenez votre exception de référence null. Il est facile de voir si vous forcez la suppression DownloadDataCompleted à toujours perdre la course en ajoutant un appel Thread.Sleep () avant de définir les données.
Les états du fil vont ressembler à ceci:
Main Thread Download Thread client.IsBusy
Waiting.... downloading... true
leaves waiting loop calls delegate false
calls data.Length data = e.Result
Il n'y a aucun moyen de savoir quel thread exécutera la dernière ligne en premier. Sur une machine multiprocesseur, les deux peuvent s'exécuter simultanément.
Puisque tout est basé sur le timing, parfois cela fonctionnera et parfois il échouera. Vous avez besoin d’une sorte de synchronisation (verrouillage) sur toutes les données auxquelles accèdent plusieurs threads.
Autres conseils
En raison du modèle de threading de winform (comme l'a souligné shf301
), j'ai modifié les codes qui me conviennent.
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/"));
}
Il y a la sortie:
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;
Ça marche pour moi?
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;
Quel est l'intérêt d'utiliser une méthode async si vous attendez le résultat dans une boucle? Utilisez simplement la version synchrone:
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();