In a MVC 4 application I have a task that copies a file to a destination folder. Because I have multiple files, I create a task for every file, and I want to wait untill they all finish. The problem is my code blocks at Task.WaitAll(copyingTasks.ToArray()) as if the tasks never end, so it never passes that line of code. Below you have the sample code:
private void CopyFilesFromWorkingCopyForProject(string projectName)
{
var copyingTasks = new List<Task>
{
CopyAllFromDirectoryToDirectory(FilesUtils.AndroidConfigsPath(), FilesUtils.AndroidPathForProject(projectName)),
CopyAllFromDirectoryToDirectory(FilesUtils.AndroidValuesPath(), FilesUtils.AndroidPathForProject(projectName)),
CopyFileToDirectory(FilesUtils.AndroidManifestPath(), FilesUtils.AndroidPathForProject(projectName)),
CopyAllFromDirectoryToDirectory(FilesUtils.IosConfigsPath(), FilesUtils.IosPathForProject(projectName))
};
Task.WaitAll(copyingTasks.ToArray());
}
private async Task CopyAllFromDirectoryToDirectory(string sourceDirectory, string destinationDirectory)
{
foreach (string filename in Directory.EnumerateFiles(sourceDirectory))
{
await CopyFileToDirectory(filename, destinationDirectory);
}
}
private async Task CopyFileToDirectory(string filename, string destinationDirectory)
{
using (FileStream sourceStream = File.Open(filename, FileMode.Open))
{
using (FileStream destinationStream = File.Create(destinationDirectory + filename.Substring(filename.LastIndexOf('\\'))))
{
await sourceStream.CopyToAsync(destinationStream);
}
}
}
If I comment Task.WaitAll(copyingTasks.ToArray()); it doesn't block anymore, but I want to wait for all the files to be copied.
Combining await and synchronous wait leads to deadlocks, because async methods try to resume on the context that's currently blocked by your wait.
What you should do instead is to make CopyFilesFromWorkingCopyForProject() also async (and the method that calls that, and the method that calls that, …):
private async Task CopyFilesFromWorkingCopyForProject(string projectName)
{
var copyingTasks = new List<Task>
{
CopyAllFromDirectoryToDirectory(FilesUtils.AndroidConfigsPath(), FilesUtils.AndroidPathForProject(projectName)),
CopyAllFromDirectoryToDirectory(FilesUtils.AndroidValuesPath(), FilesUtils.AndroidPathForProject(projectName)),
CopyFileToDirectory(FilesUtils.AndroidManifestPath(), FilesUtils.AndroidPathForProject(projectName)),
CopyAllFromDirectoryToDirectory(FilesUtils.IosConfigsPath(), FilesUtils.IosPathForProject(projectName))
};
await Task.WhenAll(copyingTasks);
}
If you can't or don't want to do that, you need to make sure the async methods don't resume on the current context. To do that, you can use ConfigureAwait(false) for all your awaits, or you can call the async methods on a background thread using Task.Run().
Related
I currently am trying to upload a pdf file of size 260kb with Swagger UI and it doesnt work. If I try to do the same thing with a small 50kb Word file it works.
My controller code is:
[HttpPost()]
public async Task<IActionResult> Upload(IFormFile file)
{
var name = SanitizeFilename(file.FileName);
if (String.IsNullOrWhiteSpace(name))
{
throw new ArgumentException();
}
using (Stream stream = file.OpenReadStream())
{
await storage.Save(stream, name);
}
return Accepted();
}
My AzureBlobStorage class's save method is:
public async Task<Task> Save(Stream fileStream, string name)
{
var blobContainer = await GetBlobContainerAsync();
CloudBlockBlob blockBlob = blobContainer.GetBlockBlobReference(name);
var task = blockBlob.UploadFromStreamAsync(fileStream);
var success = task.IsCompletedSuccessfully;
return task;
//return blockBlob.UploadFromStreamAsync(fileStream);
}
Here is some of the debug windows:
This is from the controller of the word document:
This is from the controller of the PDF document:
Notice the red/pink lettering which is different.
This is from the AzureBlobStorage save method - word document:
This is from the AzureBlobStorage save method - pdf document:
I have read the IFormFile might not do continuous streaming but how do I know if that is the issue? And if it is, what is the preferred approach?
I am not following your logic here:
public async Task<Task> Save(Stream fileStream, string name)
{
var blobContainer = await GetBlobContainerAsync();
CloudBlockBlob blockBlob = blobContainer.GetBlockBlobReference(name);
var task = blockBlob.UploadFromStreamAsync(fileStream);
var success = task.IsCompletedSuccessfully;
return task;
//return blockBlob.UploadFromStreamAsync(fileStream);
}
This is the way it should be written:
public async Task Save(Stream fileStream, string name)
{
var blobContainer = await GetBlobContainerAsync();
CloudBlockBlob blockBlob = blobContainer.GetBlockBlobReference(name);
await blockBlob.UploadFromStreamAsync(fileStream);
}
You want to await for the task to finish here before you return.
Returning Task<Task> is slightly unorthodox and doesn't make sense for what you want to do here.
Also, keep in mind, if your file is really large, Kestrel server could give up on the request. There is a timeout in the range of around 90 seconds to complete the request. So, if uploading the file takes longer than 90 seconds, the caller could receive an error (but the upload will still finish).
Typically you will dump the file to the disk, then return an Accepted to the caller. Then post the file to a background queue to upload the file. More information about that here.
I can read a file from the form-data and save it as expected. However, when I do an async action, the stream closes and the file is no longer there. Removing the async action means that it works again. I need to do an async query to save the file in the correct place.
[Route("uploadFile")]
[AllowAnonymous]
public async void uploadFile()
{
var files = HttpContext.Request.Form.Files;
if (files != null)
{
var file = files.FirstOrDefault();
var fileName = file.FileName;
using (var input = file.OpenReadStream())
{
var id = "someId";
await Task.Delay(TimeSpan.FromSeconds(2));
// after doing this async action, the file and stream are no longer accessible.
...
I posted this as a question to the asp.net core team and had an answer here.
Don't use async void in your controller action. Use async Task instead so that the framework can wait until you are done.
I didn't expect the return type to impact this functionality, but well, now I know.
I want to write Custom Middleware in my ASP.NET Core 1.0 project which will replace original framework's Http Response Stream to my own, so I will be able to perform read / seek / write operations on it (first 2 are not possible on the original stream) in the further code i.e. in Actions or Filters.
I've started with the following code:
public class ReplaceStreamMiddleware
{
protected RequestDelegate NextMiddleware;
public ReplaceStreamMiddleware(RequestDelegate next)
{
NextMiddleware = next;
}
public async Task Invoke(HttpContext httpContext)
{
using (var responseStream = new MemoryStream())
{
var fullResponse = httpContext.Response.Body;
httpContext.Response.Body = responseStream;
await NextMiddleware.Invoke(httpContext);
responseStream.Seek(0, SeekOrigin.Begin);
await responseStream.CopyToAsync(fullResponse);
}
}
}
The problem with the following code is that sometimes the fullResponse stream is already closed at the time of invoking await responseStream.CopyToAsync(fullResponse); so it throws an exception Cannot access a closed Stream.
This weird behaviour is easy to observe when I load the page in the browser and then refresh, before it loads completely.
I would like to know:
why this happens?
how to prevent it?
is my solution a good idea or there is another way to replace response stream?
The exception doesn't come from your CopyToAsync. It's from one of your code's callers:
You're not restoring the original response stream in HttpContext. Therefore, whoever calls your middleware will get back a closed MemoryStream.
Here's some working code:
app.Use(async (httpContext, next) =>
{
using (var memoryResponse = new MemoryStream())
{
var originalResponse = httpContext.Response.Body;
try
{
httpContext.Response.Body = memoryResponse;
await next.Invoke();
memoryResponse.Seek(0, SeekOrigin.Begin);
await memoryResponse.CopyToAsync(originalResponse);
}
finally
{
// This is what you're missing
httpContext.Response.Body = originalResponse;
}
}
});
app.Run(async (context) =>
{
context.Response.ContentType = "text/other";
await context.Response.WriteAsync("Hello World!");
});
I'm trying to get my head around designing a UI that remains responsive while a long running task is being executed.
To that end, I created a simple app in VS2012 and added the following class to it:
using System.Threading.Tasks;
namespace TaskTest
{
class Class1
{
public async Task<int> Async()
{
//simulate a long running process
for (long x = 0; x < long.MaxValue; x++) { }
return 1;
}
}
}
I then modified the main page's LoadState() method thusly:
protected override async void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
await DoLongRunningProcess();
}
private async Task DoLongRunningProcess()
{
var id = 0;
id = await new Class1().Async();
await new MessageDialog(id + "").ShowAsync();
}
I want the page to remain responsive while that process executes. However, when I run this code, the page takes a long time to load. What am I doing wrong?
TIA
async isn't magic; it just gives you the capability to write asynchronous code. In particular, async does not execute code on a background thread. You can use Task.Run to do this.
You may find my async/await intro or the MSDN documentation helpful.
That was helpful. I made the following changes and I got the result I was looking for:
class Class1
{
public int Launch()
{
//throw new Exception("Class1 exception");
for (var i = 0; i < int.MaxValue / 2; i++) ;
return 1;
}
}
...
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
var task = DoLongRunningProcess();
await task;
await new MessageDialog(task.Result + "").ShowAsync();
}
private Task<int> DoLongRunningProcess()
{
return Task.Run<int>(() => new Class1().Launch());
}
The page continues to load and after a short pause the message dialog is displayed. Now however, I need to know how to catch exceptions. If I uncomment the //throw new Exception ... line in method Launch(), it is reported as an unhandled exception. I want to catch this exception in the main UI thread (i.e., in the body of method LoadState) but I can't seem to manage it.
I have a working windows 8 caching solution using DataContractSerializer that raises a XmlException "Unexpected end of file" only when the UI is being used 'quickly'.
public static class CachingData<T>
{
public static async void Save(T data, string filename, StorageFolder folder = null)
{
folder = folder ?? ApplicationData.Current.LocalFolder;
try
{
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
using (IRandomAccessStream raStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
using (IOutputStream outStream = raStream.GetOutputStreamAt(0))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(outStream.AsStreamForWrite(), data);
await outStream.FlushAsync();
}
}
}
catch (Exception exc)
{
throw exc;
}
}
public static async System.Threading.Tasks.Task<T> Load(string filename, StorageFolder folder = null)
{
folder = folder ?? ApplicationData.Current.LocalFolder;
T data = default(T);
StorageFile file = await folder.GetFileAsync(filename);
using (IInputStream inStream = await file.OpenSequentialReadAsync())
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
data = (T)serializer.ReadObject(inStream.AsStreamForRead());
}
return data;
}
}
e.g. user clicks on item in list CachingData.Load is called async via await, checks for FileNotEoundException and either loads the data from disk or from the network, serialising on completion.
After first loaded user selects another item in the list and cycle repeats.
The problem occurs when "After first loaded" becomes "does not wait for load" and the item selected is not available cached.
Not quite sure how to proceed or even how to debug, hoping that just ignoring will allow the app to continue(just withough the nice speed increase of caching)