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 });
}
1 Answer 1
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>()
);
-
\$\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– Brett Caswell2015年10月13日 21:31:37 +00:00Commented Oct 13, 2015 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– Brett Caswell2015年10月26日 14:07:17 +00:00Commented Oct 26, 2015 at 14:07
Task.Run Parallel.ForEach
combination orTask.WhenAll
.. however, there are likely some IO File writting Async Methods \$\endgroup\$