1
\$\begingroup\$

I maintain an application that has a method in a class for saving uploaded files to filesystem and I want to run asynchronously as it saves multiple files in a single request. I don't know if it's best to use Task.FromResult or Task.Run, but I've modifed the code to use Task.FromResult.

public class AttachmentProcessor 
{

    public static Task<IEnumerable<Attachment>> SaveAttachmentsToFileSystemAsync(IEnumerable<HttpPostedFileBase> files,
        string cOfONumber)
    {
        if (files == null)
            return Task.FromResult(Enumerable.Empty<Attachment>());

        string topFolderPath = AppDomain.CurrentDomain.BaseDirectory + "Attachments\\";
        string folderName = cOfONumber;
        var directory = topFolderPath + folderName;
        string folderPath = directory + "\\";
        var attachments = new List<Attachment>();

        DirectoryInfo subFolder = Directory.CreateDirectory(directory);
        foreach (HttpPostedFileBase file in files)
        {
            if (file != null)
            {
                var fileName = Path.GetFileName(file.FileName);
                if (!string.IsNullOrEmpty(fileName))
                {

                    // file is saved to path
                    file.SaveAs(folderPath + fileName);

                    attachments.Add(new Attachment
                    {
                        CofONumber = cOfONumber,
                        FileName = fileName,
                        File = folderPath + fileName
                    });
                }
            }
        }

        return Task.FromResult(attachments.AsEnumerable());
    }   
}

And the calling code:

[HttpPost]
public async Task<ActionResult> Index(AttachmentViewModel model)
{
    if (ModelState.IsValid)
    {
        try
        {
            var attachments = await AttachmentProcessor.SaveAttachmentsToFileSystemAsync(model.Others, model.CofONumber).ConfigureAwait(false);

            _repository.InsertEntities(attachments.ToList());
            _unitOfWork.Save();
            return RedirectToAction("Index", "Home");
        }
        catch (Exception)
        {
            ModelState.AddModelError("", "Unable to save data");
        }

    }
    return View(new AttachmentViewModel() { CofONumber = model.CofONumber});
}

My other thoughts are: calling Task.Run() within SaveAttachmentsToFileSystemAsync method

public static async Task<IEnumerable<Attachment>> SaveAttachmentsToFileSystemAsync(IEnumerable<HttpPostedFileBase> files,
        string cOfONumber)
    {
        if (files == null) return null;

        var task = Task.Run(() =>
        {
            string topFolderPath = AppDomain.CurrentDomain.BaseDirectory + "Attachments\\";
            string folderName = cOfONumber.Replace(@"/", "_");
            var directory = topFolderPath + folderName;
            string folderPath = directory + "\\";
            var attachments = new List<Attachment>();

            DirectoryInfo subFolder = Directory.CreateDirectory(directory);
            foreach (HttpPostedFileBase file in files)
            {
                if (file != null)
                {
                    var fileName = Path.GetFileName(file.FileName);
                    if (!string.IsNullOrEmpty(fileName))
                    {

                        // file is saved to path
                        file.SaveAs(folderPath + fileName);

                        attachments.Add(new Attachment
                        {
                            CofONumber = cOfONumber,
                            FileName = fileName,
                            File = folderPath + fileName
                        });
                    }
                }
            }
            return attachments;
        });

        return await task;
    }

Or leaving it synchronous as it is and using Task.Run from the calling code:

public static IEnumerable<Attachment> SaveAttachmentsToFileSystemAsync(IEnumerable<HttpPostedFileBase> files,
    string cOfONumber)
{
    if (files == null) return null;

        string topFolderPath = AppDomain.CurrentDomain.BaseDirectory + "Attachments\\";
        string folderName = cOfONumber.Replace(@"/", "_");
        var directory = topFolderPath + folderName;
        string folderPath = directory + "\\";
        var attachments = new List<Attachment>();

        DirectoryInfo subFolder = Directory.CreateDirectory(directory);
        foreach (HttpPostedFileBase file in files)
        {
            if (file != null)
            {
                var fileName = Path.GetFileName(file.FileName);
                if (!string.IsNullOrEmpty(fileName))
                {

                    // file is saved to path
                    file.SaveAs(folderPath + fileName);

                    attachments.Add(new Attachment
                    {
                        CofONumber = cOfONumber,
                        FileName = fileName,
                        File = folderPath + fileName
                    });
                }
            }
        }
        return attachments;
}

//And using Task.Run from the controller 
[HttpPost]
public async Task<ActionResult> Index(AttachmentViewModel model)
{
    if (ModelState.IsValid)
    {
        try
        {
            var attachments = await Task.Run(() => AttachmentProcessor.meT(model.Others, model.CofONumber));

            _repository.InsertEntities(attachments.ToList());
            _unitOfWork.Save();
            return RedirectToAction("Index", "Home");
        }
        catch (Exception)
        {
            ModelState.AddModelError("", "Unable to save data");
        }

    }
    return View(new AttachmentViewModel() { CofONumber = model.CofONumber });
}
\$\endgroup\$
  • \$\begingroup\$ neiter method seems correct as written, running a task doesn't mean much if you await it in the next statement.. use Task.Run Parallel.ForEach combination or Task.WhenAll .. however, there are likely some IO File writting Async Methods \$\endgroup\$ – Brett Caswell Oct 13 '15 at 19:42
1
\$\begingroup\$

Here is an example to compare to.

    public class Payload
    {
        public string Path { get; set; }
        public string FileName { get; set; }
        public string FileExt { get; set; }
        public string Data { get; set; }

    }
    static void Main(string[] args)
    {
        IEnumerable<Payload> payloads = new Payload[]
        {
            new Payload{Path = @"C:\TestFolder\", FileName = @"file1", FileExt = ".txt", Data = @"File Contents"},
            new Payload{Path = @"C:\TestFolder\", FileName = @"file2", FileExt = ".txt", Data = @"File Contents"},
            new Payload{Path = @"C:\TestFolder\", FileName = @"file3", FileExt = ".txt", Data = @"File Contents"},
            new Payload{Path = @"C:\TestFolder\", FileName = @"file4", FileExt = ".txt", Data = @"File Contents"},
            new Payload{Path = @"C:\TestFolder\", FileName = @"file5", FileExt = ".txt", Data = @"File Contents"},
            new Payload{Path = @"C:\TestFolder\", FileName = @"file6", FileExt = ".txt", Data = @"File Contents"},
            new Payload{Path = @"C:\TestFolder\", FileName = @"file7", FileExt = ".txt", Data = @"File Contents"},
            new Payload{Path = @"C:\TestFolder\", FileName = @"file8", FileExt = ".txt", Data = @"File Contents"},
            new Payload{Path = @"C:\TestFolder\", FileName = @"file9", FileExt = ".txt", Data = @"File Contents"}
        };

        Task result = Task.WhenAll(payloads.Select(payload => Task.Run(function: async () => {
            byte[] buffer = Encoding.UTF8.GetBytes(payload.Data);
            int length = buffer.Length;

            string fullPath = payload.Path + payload.FileName + payload.FileExt;

            if (System.IO.File.Exists(path: fullPath))
            {
                if (System.IO.File.ReadLines(path: fullPath).Count() > 0)
                {
                    using (System.IO.FileStream stream = System.IO.File.Open(path: fullPath, mode: System.IO.FileMode.Append))
                    {
                        await stream.WriteAsync(buffer: buffer, offset: 0, count: length);
                    }
                }
                else
                {
                    using (System.IO.FileStream stream = System.IO.File.Open(path: fullPath, mode: System.IO.FileMode.OpenOrCreate))
                    {
                        await stream.WriteAsync(buffer: buffer, offset: 0, count: length);
                    }
                }
            } else
            {
                using (System.IO.FileStream stream = System.IO.File.Open(path: fullPath, mode: System.IO.FileMode.OpenOrCreate))
                {
                    await stream.WriteAsync(buffer: buffer, offset: 0, count: length);
                }
            }
        })));



        result.Wait();
        Console.WriteLine(value: "Press [Enter] to Proceed");

        ConsoleKey key;
        do
        {
            key = Console.ReadKey().Key;
        } while (key != ConsoleKey.Enter);
    }

this function: async () {} can be a function delegate, but note the use of payload in the context of the anonymous async function.. you be better of defining the Select Clause (payloads.Select<Payload, Task>());

\$\endgroup\$
  • \$\begingroup\$ I tested with 100 records against a synchronous version List<Payload>().ForEach( payload => { }) and each iteration of the Async Method was about half.. but when the files were still small, the affect was pretty minimum.. or some other affects were in play perhaps... \$\endgroup\$ – Brett Caswell Oct 13 '15 at 21:31
  • \$\begingroup\$ To clarifiy... I think I meant to say I tested 100 iterations of these 9 file/payload instances.. the operation is to append, so 'when the files were small' was meant to distinquish a difference in execution/elapsedTime based of the actual size of the files in bytes/kilobytes; for subsequent tests (again, being 100 append iterations) grew the files. It seems the internal seek method within the stream.WriteAsync method can be costly (somewhat expected I suppose)... \$\endgroup\$ – Brett Caswell Oct 26 '15 at 14:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.