DownloadDataAsync + Delegate C# works in main but not in class function
I ran this persons code. It worked as a main but when i put it in my class it doesnt work. Why? How to use the WebClient.DownloadDataAsync() method in this context?
It crashes bc data == null and throws an null exception :(.
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 a开发者_StackOverflow社区nd a button click
new Test2().func();
This code has a race condition on the data field. The DownloadDataCompleted anonymous delegate is called from a different thread than the data.Length call is made and at the point that DownloadDataCompleted is being called the IsBusy becomes false. It is a race between the two threads on who access data first. If the main thread calls data.Length before data is set on the download thread you get your null reference exception. It is must easy to see if you force the DownloadDataCompleted delete to always loose the race by added a Thread.Sleep() call to it before it sets data.
The thread's states will look like this:
Main Thread Download Thread client.IsBusy
Waiting.... downloading... true
leaves waiting loop calls delegate false
calls data.Length data = e.Result
There is no way of knowing which thread will run the last line first. On a multi processor machine, both of those could run simultaneously.
Since this is all based on timing sometimes it will work and some times it will fail. You need some sort of synchronization (locking) on all data that is accessed by mutliple threads.
Due to the threading model of winform (as shf301
pointed out), I've modified the codes which works for 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/"));
}
There's the 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;
It works for 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;
What's the point of using an async method if you wait for the result in a loop ? Just use the synchronous 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();
精彩评论