DownloadDataAsync + Delegate C # fonctionne dans la fonction main mais pas dans la classe

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

  •  22-07-2019
  •  | 
  •  

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();
Était-ce utile?

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();
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top