views:

752

answers:

2

My application is not exiting properly. I am just trying to print the total number of connections, after that waiting for all the upload operations to complete, and then quit gracefully.

Below is the code...

using System;
using System.Net;
using System.Threading;
using System.IO;
using System.Text;

namespace ServicePointDemo
{
    class Program
    {
        struct UploadState
        {
            public string Filename;
            public AutoResetEvent are;
        }

        static void Main(string[] args)
        {
            AutoResetEvent are = new AutoResetEvent(false);
            ServicePoint sp = ServicePointManager.FindServicePoint(new Uri("ftp://xxx.xxx.xxx.xxx/public"));

            UploadState us1 = new UploadState();
            us1.are = new AutoResetEvent(false);
            us1.Filename = @"C:\inventory.xls";

            UploadState us2 = new UploadState();
            us2.are = new AutoResetEvent(false);
            us2.Filename = @"C:\somefile.txt";

            Thread t1, t2;
            t1 = new Thread(new ParameterizedThreadStart(DoUpload));
            t2 = new Thread(new ParameterizedThreadStart(DoUpload));

            t1.Start(us1);            
            t2.Start(us2);

            Console.WriteLine("Waiting for something to trigger up");
            WaitHandle.WaitAny(new WaitHandle[] { us1.are, us2.are });            

            Console.WriteLine("CurrentConnections = {0}", sp.CurrentConnections);
            Console.WriteLine("Waiting for all operations to complete...");            
            WaitHandle.WaitAll(new WaitHandle[] { us1.are, us2.are });

            Console.WriteLine("Press enter to quit");
            Console.ReadLine();
        }

        static void DoUpload(object state)
        {
            string filename = ((UploadState)state).Filename;
            FtpWebRequest ftpRequest = (FtpWebRequest)WebRequest.Create("ftp://172.16.130.22/public/" + Path.GetFileName(filename));
            Console.WriteLine("Upload URI = {0}", ftpRequest.RequestUri.AbsoluteUri);
            ftpRequest.Method = WebRequestMethods.Ftp.UploadFile;
            ftpRequest.Credentials = new NetworkCredential("anonymous", "guest@");
            ftpRequest.Proxy = new WebProxy();
            Stream stream = null;
            FileStream file = new FileStream(filename, FileMode.Open);
            Console.WriteLine("Total file size of {0} = {1}", filename, file.Length);
            StreamReader rdr = new StreamReader(file);
            Console.WriteLine("Getting bytes of {0}", filename);
            byte[] fileBytes = Encoding.ASCII.GetBytes(rdr.ReadToEnd());
            rdr.Close();
            Console.WriteLine("Acquiring connection of {0} upload...", filename);
            try
            {
                stream = ftpRequest.GetRequestStream();
                Console.WriteLine("Upload of {0} has acquired a connection", filename);
                ((UploadState)state).are.Set();
                stream.Write(fileBytes, 0, fileBytes.Length);
                Console.WriteLine("Uploading {0} complete", filename);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception has occurred: {0}", ex.Message);                
            }
            finally
            {
                Console.WriteLine("Ending uploading {0}", filename);
                stream.Close();
                ((UploadState)state).are.Set();                
            }
            Console.WriteLine("Quit DoUpload() for {0}", filename);//...is not executed(?)
        }
    }
}
A: 

You seem to have a race condition on your AutoResetEvents

If the threads run quickly they could call Set() on the auto reset event twice before the main thread has reached the first WaitAny(). This would then allow it to pass the first WaitAny() but will block on the WaitAll() because neither thread does any more calls to Set(). (You can simulate this by putting a long Thread.Sleep() in the main thread right after you have started the two background threads)

I would suggest you use two separate AutoResetEvents (Or just plain EventWaitHandles) one that is Set() when the connection is acquired, and one that is set when it is complete, so you don't rely on the timing of the threads.

Simon P Stevens
A: 

Replace the WaitAll() call with a call to Join() on each thread.

By the way, you don't need the very first AutoResetEvent are in Main() as it is not used.

Matt Davis