DownloadDataAsync + Delegate C # funziona nella funzione principale ma non nella classe

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

  •  22-07-2019
  •  | 
  •  

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();
È stato utile?

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();
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top