tags:

views:

1787

answers:

13

Hi,

I have in my Form constructor, after the InitializeComponent the following code:

using (WebClient client = new WebClient())
{
    client.DownloadDataCompleted += new DownloadDataCompletedEventHandler(client_DownloadDataCompleted);
    client.DownloadDataAsync("http://example.com/version.txt");
}

When I start my form, the UI doesn't appears till client_DownloadDataCompleted is raised. The client_DownloadDataCompleted method is empty, so there's no problem there.

What I'm doing wrong? How is supposed to do this without freezing the UI?

Thanks for your time.
Best regards.

FULL CODE:

Program.cs

using System;
using System.Windows.Forms;

namespace Lala
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Form1.cs

using System;
using System.Net;
using System.Windows.Forms;

namespace Lala
{
    public partial class Form1 : Form
    {
        WebClient client = new WebClient();

        public Form1()
        {
            client.DownloadDataCompleted += new DownloadDataCompletedEventHandler(client_DownloadDataCompleted);
            client.DownloadDataAsync(new Uri("http://www.google.com"));
            InitializeComponent();
        }

        void client_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
        {
            textBox1.Text += "A";
        }
    }

    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(12, 12);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(12, 41);
            this.textBox1.Multiline = true;
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(468, 213);
            this.textBox1.TabIndex = 1;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(492, 266);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.button1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.TextBox textBox1;
    }
}
+1  A: 

You want to run the download in a different thread, see this as a starting point.

Andrew Bullock
So ... what is the Async for? If i'm going to use a BackgroundWorker, why should I use DownloadDataAsync instead of DownloadData?
Matías
A: 

I've tried your code and it works fine.

Could you post your Main(Args[]) method and the value of a and b when this is run:

    int a, b;
    ThreadPool.GetMaxThreads(out a, out b);

I tried it in .NET 3.5 and VS2008. I'm at a loss, but I am convinced it's to do with the setup on your machine. Not the code. Check these things:

  • Check the thread pool (above). I get a=250 b=1000
  • Disable all third party plugins
  • Load VS "Clean" (Have you rebooted)
  • Close as many programs/services as you can
  • Check your IE config. I think that class uses IE code/settings
  • Firewall? AntiVirus?
  • Try it on another computer
Robert Wagner
@Matias: It shouldn't be up to responders to provide a complete program demonstrating the problem - please help us to help you by making it easy for us to reproduce.
Jon Skeet
That code is not working for me. I'm getting the END OF APP 1ms before the downloaded string. It's taking like 5 seconds to get to "END OF APP"
Matías
@Matias I tried your code and the UI is full responsive. Post your static main method/class.
Robert Wagner
If I use your code before the Application.Run(new Form1()); I get a=250 and b=1000.
Matías
- a=250, b=1000 - There's no plugins, i'm on VS2008 Express ... but I've tried in another machine were I've installed VS2008 Pro yesterday, and I'm getting the same result. - There's no change. - I've tried disabling all in msconfig
Matías
IE settings seems just fine. No proxy, only autodetect config enabled just in case - No firewall (i'm behind a router, with wifi, there's no packet loss, and I have 169 ms stable to google) - NOD antivirus disabled
Matías
A: 

That looks a little weird to me.

Try keeping a member ref of the WebClient so you don't destroy it in the constructor, maybe it's blocking on the client.Dispose()

CVertex
No, I'm getting the same problem.
Matías
+1  A: 

UNDELETED: As many think about the using block like I do, I've confirmed that it is not related.

Can you remove the using block, I think it is waiting to dispose the webclient instance.

chakrit
Nope, same result without the using.
Matías
A: 

I strongly suspect that it's to do with disposing of the WebClient while you're still using it for an asynchronous call.

Try removing the using statement, and call Dispose in an event handler instead. (Or just for testing, don't worry about disposing it at all.

If you could post a short but complete program which demonstrates the issue, that would be really handy.

Jon Skeet
I've posted the full code. You can check it now.
Matías
Thanks for the code. This is odd. Do you have a URL which takes a little longer than www.google.com to reply? (That's particularly quick for me as I'm within Google :)
Jon Skeet
hehe, you may wanna try a slow loading page like http://cablemodem.fibertel.com.ar
Matías
@Matias: It's only using the first request (not the whole page including images etc) so that's quick. Will try to find a large file to download... or use my netbook over 3G :)
Jon Skeet
A: 

The using() statement is trying to call Dispose() of the WebClient while it is still downloading. The Dispose method probably waits for the download to finish before continuing.

Try not using a using() statement and dispose of the WebClient in your DownloadDataCompleted event.

Stephan Leclercq
The problem persists.
Matías
+1  A: 

As well as the disposing of something which is possibly still running the async call that's been mentioned by other people, I would STRONGLY recommend against doing heavyweight stuff like this in a form's constructor.

Do it in an OnLoad override instead, where you will also be able to check the DesignMode property which will help you avoid several levels of hell with the VS forms designer.

Will Dean
Same result overriding OnLoad and using Load (not at the same time)
Matías
A: 

I can run your code fine. And the form shows up and the download is completed AFTER the form showed up.

I do not have any freezes as you mentioned.

I think it has something to do with the environment you are running it inside.

What version of .NET/Visual Studio are you on?

chakrit
Visual Studio 2008 Pro (with .net 3.5) and Express ... with the same result in both
Matías
A: 

Ummm.... I am just curious

Do you have any Firewalls on?

any firewalls at all on your machine?

Maybe ZoneAlarm?

chakrit
No. But I'm behind a router (using wifi). I don't have packet loss to the router or to google. I'm getting 1ms stable to the router, and 169ms to google.
Matías
+1  A: 

Now that we've got full code, I can say I'm definitely not seeing the problem - not quite as described, anyway.

I've got a bit of logging to indicate just before and after the DownloadDataAsync calls, and when the completed handler is fired. If I download a large file over 3G, there is a pause between "before" and "after" but the UI comes up ages before the file completes downloading.

I have a suspicion that the connect is done synchronously, but the actual download is asynchronous. That's still unfortunate, of course - and possibly punting all of that into a different thread is the way to go - but if I'm right it's at least worth knowing about.

Jon Skeet
so, what's the best or correct way to put his on a different thread?
Matías
BackgroundWorker would probably be the easiest way so that you can then get back to the UI thread easily. Either that or just start a new thread or use the thread pool.
Jon Skeet
If you're right and the connect is done synchronously, that would make sense -- it makes handling the most common exception (failure to connect) very easy to do. Handling exceptions on async callbacks is much more complex, but exceptions during downloads are probably rarer.
technophile
On the other hand it would be a real pain in terms of making it truly async - and surely you have to be able to cope with exceptions during downloads anyway :(
Jon Skeet
I've read at least that the DNS resolution is done synchronously before the asynchronous HTTP request fires. See http://www.experts-exchange.com/Web_Development/Software/Q_21982212.html
mbeckish
That would certainly make sense, yes.
Jon Skeet
+1  A: 

DownloadDataAsync vs. DownloadData in a non-UI thread:

DownloadDataAsync is nice because it doesn't actually tie up a thread until handling the DownloadDataCompletedEvent, after the request has been made and the server responds.

I believe Jon Skeet is on the right track - I've read that DNS resolution must complete synchronously before the asynchronous HTTP request is queued up and the DownloadDataAsync call returns.

Could the DNS resolution be slow?

mbeckish
A: 

In my experience, it sort-of blocks the thread when running debugging the project (running it inside Visual Studio) and when accessing the server for the first time.

When running the compiled exe, the blocking is not perceivable.

Dan
A: 

I've just tested the same thing in a WPF project under VS2010, .NET 4.

I'm downloading a file with a progress bar to show percentage completed using WebClient.DownloadDataCompleted etc.

And, to my amazement, I'm finding the same thing @Dan mentioned: Within the debugger it blocks the thread in a funny way. In debug, my progress meter gets updated at 1%, then does nothing for a while, then updates again suddenly at 100%. (Debug.WriteLn statements print smoothly throughout). And between these two times, the UI is frozen.

But outside the debugger, the progress bar moves smoothly from 0% to 100%, and the UI never freezes. Which is what you'd expect.

Adrian