DownloadDataAsync + Delegierter C # funktioniert in Haupt aber nicht in Klassenfunktion
Frage
Ich lief diese Personen Code. Es funktionierte als Haupt, aber wenn ich es in meiner Klasse setzen es funktioniert nicht. Warum? Wie die WebClient.DownloadDataAsync verwenden ( ) Verfahren in diesem Zusammenhang?
Es stürzt bc Daten == null und löst eine Null Ausnahme: (
.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();
Lösung
Dieser Code hat ein race condition auf dem Datenfeld rel="nofollow. Die anonym DownloadDataCompleted Delegierten werden von einem anderen Thread aufgerufen als der data.Length Anruf getätigt wird, und an dem Punkt, dass die DownloadDataCompleted IsBusy falsch werden aufgerufen werden. Es ist ein Rennen zwischen den beiden Threads auf die Daten zuerst zugreifen. Wenn der Haupt-Thread data.Length aufruft, bevor die Daten auf den Download-Thread gesetzt bekommen Sie Ihre NULL-Verweis Ausnahme. Es ist ein Muss leicht zu sehen, wenn Sie die DownloadDataCompleted löschen zwingen immer das Rennen zu verlieren, indem hinzugefügt einem Thread.Sleep () aufrufen, um ihn, bevor er Datensätze.
Die Staaten Thread wird wie folgt aussehen:
Main Thread Download Thread client.IsBusy
Waiting.... downloading... true
leaves waiting loop calls delegate false
calls data.Length data = e.Result
Es gibt keine Möglichkeit, zu wissen, welcher Thread zuerst die letzte Zeile ausgeführt wird. Auf einer Multi-Prozessor-Maschine könnte diese beide gleichzeitig ausgeführt werden.
Da dies alles auf Timing basiert manchmal funktioniert es und manchmal wird es scheitern. Sie brauchen eine Art der Synchronisation (Verriegelung) auf alle Daten, die von mutliple Threads zugegriffen wird.
Andere Tipps
Durch das Threading-Modell von winform (wie shf301
darauf hingewiesen), habe ich die Codes geändert, die für mich funktioniert.
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/"));
}
Es gibt die Ausgabe:
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;
Es funktioniert für mich?
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;
Was ist der Sinn der Verwendung einer Asynchron-Methode, wenn Sie in einer Schleife auf das Ergebnis warten? Verwenden Sie einfach die synchrone Version:
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();