views:

445

answers:

1

Hello everyone,

I am using VSTS 2008 + C# + .Net 3.5 + ASP.Net + IIS 7.0 to develop a Windows Forms application at client side to upload a file, and at server side I receive this file using an aspx file.

I find my client side application will hang after click the button to trigger upload event. Any ideas what is wrong and how to solve? Thanks!

Client side code,

  public partial class Form1 : Form
    {
        private static WebClient client = new WebClient();
        private static ManualResetEvent uploadLock = new ManualResetEvent(false);

        private static void Upload()
        {
            try
            {
                Uri uri = new Uri("http://localhost/Default2.aspx");
                String filename = @"C:\Test\1.dat";

                client.Headers.Add("UserAgent", "TestAgent");
                client.UploadProgressChanged += new UploadProgressChangedEventHandler(UploadProgressCallback);
                client.UploadFileCompleted += new UploadFileCompletedEventHandler(UploadFileCompleteCallback);
                client.UploadFileAsync(uri, "POST", filename);
                uploadLock.WaitOne();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.StackTrace.ToString());
            }
        }

        public static void UploadFileCompleteCallback(object sender, UploadFileCompletedEventArgs e)
        {
            Console.WriteLine("Completed! ");
            uploadLock.Set();
        }

        private static void UploadProgressCallback(object sender, UploadProgressChangedEventArgs e)
        {
            Console.WriteLine("{0}    uploaded {1} of {2} bytes. {3} % complete...",
                (string)e.UserState,
                e.BytesSent,
                e.TotalBytesToSend,
                e.ProgressPercentage);

            // Console.WriteLine (e.ProgressPercentage);
        }

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Upload();
        }
    }

Server side code:

    protected void Page_Load(object sender, EventArgs e)
    {
        string agent = HttpContext.Current.Request.Headers["UserAgent"];
        using (FileStream file = new FileStream(@"C:\Test\Agent.txt", FileMode.Append, FileAccess.Write))
        {
            byte[] buf = Encoding.UTF8.GetBytes(agent);
            file.Write(buf, 0, buf.Length);
        }

        foreach (string f in Request.Files.AllKeys)
        {
            HttpPostedFile file = Request.Files[f];
            file.SaveAs("C:\\Test\\UploadFile.dat");
        }
    }

thanks in avdance, George

+2  A: 

you are waiting in the main windows events thread, so your GUI will be frozen.

Try this (using non static methods allows you to use the Control.Invoke method to run callbacks on the windows GUI thread and free this thread in order to redraw)

public partial class Form1 : Form
{
    private static WebClient client = new WebClient();
    private static ManualResetEvent uploadLock = new ManualResetEvent(false);

    private void Upload()
    {
        try
        {
            Cursor=Cursors.Wait;
            Uri uri = new Uri("http://localhost/Default2.aspx");
            String filename = @"C:\Test\1.dat";

            client.Headers.Add("UserAgent", "TestAgent");
            client.UploadProgressChanged += new UploadProgressChangedEventHandler(UploadProgressCallback);
            client.UploadFileCompleted += new UploadFileCompletedEventHandler(UploadFileCompleteCallback);
             client.UploadFileAsync(uri, "POST", filename);    
        }
        catch (Exception e)
        {
            Console.WriteLine(e.StackTrace.ToString());
            this.Cursor=Cursors.Default;
            this.Enabled=false;
        }
    }

    public void UploadFileCompleteCallback(object sender, UploadFileCompletedEventArgs e)
    {
      // this callback will be invoked by the async upload handler on a ThreadPool thread, so we cannot touch anything GUI-related. For this we have to switch to the GUI thread using control.BeginInvoke
      if(this.InvokeRequired)
      {
           // so this is called in the main GUI thread
           this.BeginInvoke(new UploadFileCompletedEventHandler(UploadFileCompleteCallback); // beginInvoke frees up the threadpool thread faster. Invoke would wait for completion of the callback before returning.
      }
      else
      {
          Cursor=Cursors.Default;
          this.enabled=true;
          MessageBox.Show(this,"Upload done","Done");
      }
public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Upload();
        }
    }
}

And do the same thing in your progress (you could update a progressbar indicator for example).

Cheers, Florian

Florian Doyon
Why waiting in the main windows events thread will make GUI will be frozen? Do you have more related document on this subject to recommend to read?
George2
The Windows GUI are message-driven, although WinForms hides this from you. Each Windows app uses a 'Message Pumping' thread that will deliver messages to the GUI widgets (such as mouse moved, user click, key pressed, and much more0. Your handlers run in the main windows message thread, thus if you are waiting on an event, you will block the windows message pump and the gui will appear 'frozen'. You usually can tell when it can't redraw itself for example.See here for a great resource: http://msdn.microsoft.com/en-us/library/3s8xdz5c%28VS.80%29.aspx
Florian Doyon
Where do you invoke Upload method?
George2
Exactly as you did put it, in the click handler. I'll update the code above.
Florian Doyon
But why do you have if-else block in UploadFileCompleteCallback? I think this method will only be called in one scenario, i.e. when upload is done. I do not know why you need to check "InvokeRequired"?
George2
Bear with me :) This callback (UploadFileCompleteCallback) will be invoked on a thread that belongs to the ThreadPool. Because the way Winforms works under the hood, you can only access winforms controls from the winforms thread. As the callback is called in the threadpool the 1st time, we detect that if we want to talk to winforms we need to switch threads(InvokeRequired), then if it is, we actually do the switch(BeginInvoke) and call ourselves. So the 2nd time this callback is executed, it's in the winforms thread, and InvokeRequired returns false, so we can actually do GUI stuff.
Florian Doyon
Thanks! 1. I read your code and understand your code. Let me finally confirm with you whether my understanding is correct before marking your reply as answered. :-) My understanding is, UploadFileCompleteCallback will be called the first time when the file actually completed, then you are using recursive call to call UploadFileCompleteCallback again using UI thread. This time you display the message box. Correct understanding? 2. I noticed you mentioned two terms -- UI thread and main (windows message) thread, are they the same thing? If not, what are the differences?
George2
Yes, that's it, Main thread and UI thread are the same threads in this case. Just one nitpicking, it's not actually recursion as the UploadFileCompleteCallback will call UploadFileCompleteCallback on another thread, thus using another stack.Cheers,Florian
Florian Doyon