0

Is there an efficient way to download a file and save it on disk in a "background" way without blocking the current execution of an action in a mvc controller?

Currently I have the following example code working:

    public ActionResult Index()
    {
        InitiateDownload();

        var model = GetModel();
        return View(model);
    }

    private void InitiateDownload()
    {
        Task.Run(() => DownloadAndSaveFileAsync()).ConfigureAwait(false);
    }
    private async Task DownloadAndSaveFileAsync()
    {
        var response = await GetResponse();

        using (var fileStream = new FileStream("c:\\file.zip", FileMode.Create, FileAccess.Write, FileShare.None))
        {
            await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
        }
    }
    public async Task<HttpResponseMessage> GetResponse()
    {
        var client = new HttpClient();
        client.BaseAddress = new Uri("http://someUrl.com");

        return await client.GetAsync("someResourceEndPoint").ConfigureAwait(false);
    }

I have read several places that you should not use Task.Run on the server (in the worker thread) so I was wondering if this example is scaleable (eg. if it receives 10 requests every second) or reliable or if there are any appropriate way to make it so?

Thanks

Vindberg
  • 1,502
  • 2
  • 15
  • 27

1 Answers1

6

without blocking the current execution of an action in a mvc controller?

The answer depends on exactly what you mean by this.

If you mean allowing you to build the model while the download is in progress, then you can do it rather simply as such:

public async Task<ActionResult> Index()
{
    var task = DownloadAndSaveFileAsync();
    var model = GetModel();
    await task;
    return View(model);
}

However, if you mean that you want to return a response to the client without waiting for the download, then that's much more complicated. I have a blog post that goes into more detail on the subject. If you do want to return a response before the work is done, then you need to decide how important that work is to you.

If you need the download to happen, then you'll need to put the download request in a reliable queue (e.g., Azure queue or MSMQ) and have an independent backend (e.g., Azure worker role or Win32 service) doing the actual downloading and saving.

If it's not as important to have the downloads complete - i.e., your system can handle the occasional missing download - then you can use the BackgroundTaskManager type from my blog post above:

private void InitiateDownload()
{
  BackgroundTaskManager.Run(() => DownloadAndSaveFileAsync());
}

I also recommend you use the BackgroundTaskManager.Shutdown cancellation token to respond appropriately when the AppDomain is being shut down before the download completes (e.g., write a message to the event log).


Whichever solution you choose, you should change DownloadAndSaveFileAsync to open the file stream for asynchronous access. This is a common mistake with file streams; by default they are synchronous.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks Stephen I will use the BackgroundTaskManager. Its of no concern if the task fails. "Open the file stream for ascynchronous access" do you mean adding useAsync:true parameter in "new FileStream(...)"? – Vindberg Mar 28 '14 at 13:26
  • Yes, `useAsync` will open the file for asynchronous access. – Stephen Cleary Mar 28 '14 at 13:28
  • @StephenCleary in which namespace I can find BackgroundTaskManager? – GibboK Jun 12 '14 at 13:30
  • 3
    The code is [now on GitHub](https://github.com/StephenCleary/AspNetBackgroundTasks), but note that .NET 4.5.2 has introduced `HostingEnvironment.QueueBackgroundWorkItem` which is almost exactly the same. – Stephen Cleary Jun 12 '14 at 13:34
  • Just for clarification: Shouldn't `ActionResult Index()` be declared as `async` when using `await` in it's implementation? – Maddin Jan 18 '22 at 12:31
  • @Maddin: Yes, thanks! – Stephen Cleary Jan 18 '22 at 13:35
  • Ok. With `async Task Index()` it's no longer synchronus, as asked in the question, right? So I wouldn't be able to call it from a synchronus method, without again using `Task.Run` or something similar? (Sorry if it's a foolish question - I'm still trying to get the fundamentals of parallel programming) – Maddin Jan 18 '22 at 14:16
  • 1
    @Maddin: The question asked how to do other work while `DownloadAndSaveFileAsync` is in progress, which the `Index` method will do. In general, if you want to use asynchronous concurrency (not "parallelism"), then you can call several *Async methods and then do an `await Task.WhenAll` on all those tasks. – Stephen Cleary Jan 18 '22 at 14:45