DownloadDataAsync + Delegate C # funciona en la función principal pero no en la clase
Pregunta
Ejecuté este código de personas. Funcionó como principal pero cuando lo puse en mi clase no funciona. ¿Por qué? Cómo usar el WebClient.DownloadDataAsync ( ) método en este contexto?
Se bloquea bc data == null y genera una excepción nula :(.
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();
Solución
Este código tiene una condición de carrera en el campo de datos. El delegado anónimo DownloadDataCompleted se llama desde un hilo diferente al de los datos. Se realiza una llamada de longitud y en el momento en que se llama DownloadDataCompleted, IsBusy se vuelve falso. Es una carrera entre los dos hilos sobre quién accede primero a los datos. Si el hilo principal llama a datos. Longitud antes de que los datos se establezcan en el hilo de descarga, obtiene su excepción de referencia nula. Es fácil ver si fuerza la eliminación de DownloadDataCompleted para perder siempre la carrera agregando una llamada Thread.Sleep () antes de que establezca los datos.
Los estados del hilo se verán así:
Main Thread Download Thread client.IsBusy
Waiting.... downloading... true
leaves waiting loop calls delegate false
calls data.Length data = e.Result
No hay forma de saber qué hilo ejecutará la última línea primero. En una máquina multiprocesador, ambos podrían ejecutarse simultáneamente.
Como todo esto se basa en el tiempo, a veces funcionará y otras fallará. Necesita algún tipo de sincronización (bloqueo) en todos los datos a los que acceden múltiples hilos.
Otros consejos
Debido al modelo de subprocesos de winform (como señaló shf301
), he modificado los códigos que funcionan para mí.
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/"));
}
Ahí está la salida:
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;
¿Funciona para mí?
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;
¿Cuál es el punto de usar un método asíncrono si espera el resultado en un bucle? Solo usa la versión síncrona:
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();