tags:

views:

258

answers:

4

I ran this persons code. It worked as a main but when i put it in my class it doesnt work. Why? http://stackoverflow.com/questions/1585985/how-to-use-the-webclient-downloaddataasync-method-in-this-context/1586014#1586014

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("http://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();
A: 

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;
Nathan Howell
It works in a simple Console app, not in a WinForm.
o.k.w
A: 

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();
Thomas Levesque
Point was to leave the code the same. It was a test and it failed in a class. I dont now why.
acidzombie24
Synch version halts the GUI thread. Hanging whatever app is using it.
Wolf5
@Wolf5, using the async version and *synchronously* waiting for it to complete (which is what the OP is doing) has the exact same effect... re-read the original code
Thomas Levesque
@thomas, You are right. Though if he uses a DoEvents() in that wait loop the GUI thread won't lock while waiting (he can move the window around etc if its winforms). Not sure if that would work on the synch version.
Wolf5
+3  A: 

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.

shf301
Perfect. Thats actually pretty logical once i think about it. But i dont know why it works on the console app. The test code does work and i'll continue on. Thanks :)
acidzombie24
@acidzombie24: The code you copied from is a for a console app, it runs the procedure and exit. Hence the author had to introduce some form of mechanism to 'stall' the app from exiting un-gracefully before download completes. For Windows Form, the form model allows you to run async operations without worrying about the app exiting, hence no need for the 'stalling' trick. See my code in my answer on this page which works in Winform.
o.k.w
+1  A: 

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;
o.k.w