Enabling Multi-threaded Upload via ActiveX/Java Uploader in ASP.NET

By the nature of HTTP, protocol web servers are designed to handle multiple simultaneous requests. While creating a file upload solution we can employ this feature to increase upload speed. The idea is quite simple: divide data into several pieces and upload them simultaneously, each data piece within a separate HTTP request. This trick can yield up to a 50% increase in upload speed if the channel between a client computer and a web server is fast enough. ActiveX/Java Uploader supports this approach, and this topic explains what changes are required to switch your application to multi-threaded upload.

Multi-threaded upload works only if the packages and/or chunks modes are turned on (to do this, the FilesPerPackage property and/or the ChunkSize property must have a positive value). In this case, the uploading files are processed in multiple pieces (by packages or chunks) rather than all at once. While uploading each file in a separate request (FilesPerPackage = 1) is the simplest way to start with multi-threaded upload, we recommend you to read the Sending All Files at Once or in Several Parts in ActiveX/Java Uploader topic. Also the Upload Modes Best Practices topic can help you to find the most appropriate configuration.

Even though ActiveX/Java Uploader supports up to 10 simultaneous threads, in real world use we have found that the optimal number of connections to use is 3-4. Uploading files in a larger number of simultaneous requests can stress your server too much which will result in the elimination of the desired positive effect.

The Saving Uploaded Files in ActiveX/Java Uploader ASP.NET topic explains how to process uploaded files and outlines three approaches for handling files on the server side. Here we will examine each approach and suggest changes required to work in the multi-threaded mode:

Processing Uploaded Data Automatically

ActiveX/Java Uploader ASP.NET classes allow the saving of uploaded data automatically to the specified folder. To enable multi-threaded upload along with Autosave Uploader.AutoSave UploadRequest.AutoSave UploadHandler.AutoSave , you do not need to change your server code. Just set the MaxConnectionCount property to a positive value (specifying a number of simultaneous connections). Make sure that you enable multiple packages and/or chunk upload as we discussed before.

The snippet below sends files in up to 4 simultaneous threads (one file per package) and saves received data to the /Catalog folder:

ASP.NET
<aur:Uploader ID="Uploader1" runat="server" AutoSave="True" 
    DestinationFolder="~/Catalog">
    <UploadSettings MaxConnectionCount="4" FilesPerPackage="1" />
</aur:Uploader>

Processing Uploaded Data Using Server-Side Components

ActiveX/Java Uploader has three server-side classes which give you flexible typed access to all data uploaded from client computers: Uploader, UploadRequest, and UploadHandler (for more information see the Saving Uploaded Files in ActiveX/Java Uploader ASP.NET topic). All components have a similar API and expose the FileUploaded Uploader.FileUploaded UploadRequest.FileUploaded UploadHandler.FileUploaded and AllFilesUploaded Uploader.AllFilesUploaded UploadRequest.AllFilesUploaded UploadHandler.AllFilesUploaded events to handle uploaded files.

The AllFilesUploaded Uploader.AllFilesUploaded UploadRequest.AllFilesUploaded UploadHandler.AllFilesUploaded event fires when the whole upload session is completed, i.e. while the upload process is in progress the server collects all data sent in single or multiple threads, and then returns a collection of uploaded files and metadata to the event handler. Thus, if you use the AllFilesUploaded Uploader.AllFilesUploaded UploadRequest.AllFilesUploaded UploadHandler.AllFilesUploaded event, you can enable multi-threaded upload by setting the MaxConnectionCount property to a positive value. Also, do not forget to enable multiple packages and/or chunk upload as we discussed at the beginning of this topic.

The FileUploaded Uploader.FileUploaded UploadRequest.FileUploaded UploadHandler.FileUploaded event fires each time a file is successfully uploaded. If you set up ActiveX/Java Uploader to transfer files in the multi-threaded mode, it can turn out that multiple requests arrive to the server at the same moment and the event handler fires concurrently for each of them. So, if you want to enable multi-threaded upload in your application and you have a FileUploaded Uploader.FileUploaded UploadRequest.FileUploaded UploadHandler.FileUploaded event handler in your code, it is not enough just to change a value of the MaxConnectionCount property. You should review code of the FileUploaded Uploader.FileUploaded UploadRequest.FileUploaded UploadHandler.FileUploaded event handler and rewrite any thread-unsafe segments.

Here you can see several examples of thread-unsafe code:

  • changing a resource that is not supposed to be changed concurrently (e.g. if you write to a file)
  • using thread-unsafe collections
  • using thread-unsafe libraries

For example, if the FileUploaded Uploader.FileUploaded UploadRequest.FileUploaded UploadHandler.FileUploaded event handler writes data to a file, there can be multiple concurrent event handler calls causing simultaneous file writing. To resolve this issue add synchronization to the file accessing code. The following snippet demonstrates how to do this:

C#
private static readonly object _sync = new object();

protected void Uploader1_FileUploaded(object sender, Aurigma.ImageUploader.FileUploadedEventArgs e)
{
    string absPath = System.IO.Path.Combine(Server.MapPath("Catalog2/"), e.UploadedFile.RelativePath);
    
    System.IO.DirectoryInfo destFolder = new System.IO.DirectoryInfo(absPath);
    if (!destFolder.Exists)
        destFolder.Create();

    string fileName = System.IO.Path.Combine(destFolder.FullName, e.UploadedFile.SourceName);
    e.UploadedFile.ConvertedFiles[0].SaveAs(fileName);

    //Save file info.
    System.Xml.XmlDocument descriptions = new System.Xml.XmlDocument();
    
    //Lock the object.
    lock (_sync)
    {
        if (System.IO.File.Exists(absPath + "Descriptions.xml"))
            descriptions.Load(absPath + "Descriptions.xml");
        else
            descriptions.AppendChild(descriptions.CreateElement("files"));

        System.Xml.XmlElement file = descriptions.CreateElement("file");
        file.SetAttribute("name", fileName);
        file.SetAttribute("width", Convert.ToString(e.UploadedFile.SourceWidth));
        file.SetAttribute("height", Convert.ToString(e.UploadedFile.SourceHeight));
        file.SetAttribute("description", e.UploadedFile.Description);
        descriptions.DocumentElement.AppendChild(file);

        //Save xml file with uploaded files info.
        descriptions.Save(absPath + "Descriptions.xml");
    }
}

Processing Uploaded Data Using POST Fields Data

This approach requires you to write code for parsing HTTP POST fields. All data sent by ActiveX/Java Uploader is stored in the intrinsic Request object which is passed to the Page_Load event handler (for more information see the Saving Uploaded Files in ActiveX/Java Uploader ASP.NET and POST Field Reference topics).

A Page_Load handler is called for each HTTP POST request. Like in the previous approach, we should take care of simultaneous calls to the event handler which can possibly cause problems in the case of thread-unsafe code. So, if you want to enable multi-threaded upload for this approach, you should perform the following steps:

  • Specify a number of simultaneous connections using the MaxConnectionCount property.
  • Make sure that you enable package upload as we discussed before. As this approach does not restore files from chunks automatically, we do not recommend using the chunk upload mode, as you would be required to write code for handling chunks.
  • Review the code of the Page_Load event handler and rewrite thread-unsafe segments. You can see examples of thread-unsafe code in the previous example.

The following snippet contains thread-safe code that saves information about uploaded files to an XML file:

C#
private static readonly object _sync = new object();

protected void Page_Load(object sender, EventArgs e)
{
    if (System.String.Equals(Request.RequestType, "POST"))
    {
        string galleryPath = Server.MapPath("Gallery/");
        for (int i = 0; i < int.Parse(Request[PostFields.PackageFileCount]); i++)
        {
            string sourceFileName = GetSafeFileName(galleryPath, Request[string.Format(PostFields.SourceName, i)]);
            //Lock the object.
            lock (_sync)
            {
                //Save file info
                System.Xml.XmlDocument descriptions = new System.Xml.XmlDocument();
                if (System.IO.File.Exists(galleryPath + "Descriptions.xml"))
                    descriptions.Load(galleryPath + "Descriptions.xml");
                else
                    descriptions.AppendChild(descriptions.CreateElement("files"));

                System.Xml.XmlElement file = descriptions.CreateElement("file");
                file.SetAttribute("name", sourceFileName);
                file.SetAttribute("width", Request[string.Format(PostFields.SourceWidth, i)]);
                file.SetAttribute("height", Request[string.Format(PostFields.SourceHeight, i)]);
                file.SetAttribute("description", Request[string.Format(PostFields.Description, i)]);
                descriptions.DocumentElement.AppendChild(file);

                //Save xml file with uploaded files info.
                descriptions.Save(galleryPath + "Descriptions.xml");
            }
        }
    }
}

private string GetSafeFileName(string path, string fileName)
{
    // ...
}

See Also

Reference

Manual