2

I'm writing a simple console app that copies files from one place to another. I would like to do the copying asynchronously. It mostly seems to work, but some of the files being copied end up being zero length files or smaller than they should be, and cannot be opened.

Here is the relevant part of my code:

static async void CopyFiles()
{
    foreach (string myFileName in filenameList)  // get a list of files to copy.
    {
        Task xx = CopyDocumentAsync(myFileName);
        xx.Wait(); // without this Wait some of the files are not copied properly.
    }
}

static async Task CopyDocumentAsync(string myFileName)
{
    if (!String.IsNullOrEmpty(myFileName))
    {
        using (FileStream source = File.Open(sourcePath +"\\" + myFileName, FileMode.Open))
        {
            using (FileStream destination = File.Create(destinationPath + "\\" + myFileName))
            {
                await source.CopyToAsync(destination);
            }
        }
    }
}

I'm guessing that the problem is that the application is exiting before all the asynchronous copying has completed, but I'm not sure how to prevent that. I've added code to wait for each copy to complete copying before moving on, but that feels like it's defeating the point of doing the work asynchronously!

Is there a way to make this work so that all the file copies can be performed in parallel, and then either wait for every copy to be finished before closing the app, or allow the app to close without terminating the copy operation?

paulH
  • 1,102
  • 16
  • 43

2 Answers2

4

You use async void for CopyFiles which means it is a fire and forget task.

Your parent calling method will return before CopyFiles has finished running. This is why the app exits before the copying of the files is done.

Change it to an awaitable method:

   static async Task CopyFiles()

Then you can await the call in your parent method.

If you can't await the call in your parent method (eg. because it's an overridden method), then you can use WaitAndUnwrapException on the task.

Community
  • 1
  • 1
thumbmunkeys
  • 20,606
  • 8
  • 62
  • 110
  • This was useful information but I couldn't add await to the parent method, and the WaitAndUnwrapException library seemed way too much code for what I wanted, and too much to get my head around. – paulH Sep 15 '15 at 14:42
2

Your function CopyFiles is declared async.

  • All async functions should return Task instead of void and Task<TResult> instead of TResult
  • The only async function that may return void is the event handler.
  • If you await the return of an async function, the return value is the void or the TResult.
  • If you have nothing to do while the other task is running, just await for the task
  • However if you do have other things to do, do this other things, while the task is running and await for it when you need the result of the task
  • One of the things you might want to do is start other tasks.
  • If you need to wait until all started tasks are finished, await Task.WhenAll

If you want to copy several documents simultaneously your code would be as follows:

private async void OnButton1_Clicked(object sender, ...)
{
    // prevent pressing the button while the files are being copied:
    this.button1.Enabled = false;
    // start copying files and wait until ready
    // Because this function is async, the UI remains responsive
    await this.CopyFiles();
    this.button1.Enabled = true;
}

// return Task instead of void!
private async Task CopyFiles()
{
    List<Task> runningTasks = new List<Task>();
    foreach (string myFileName in this.GetFilenameList()) 
    {
        runningTasks.Add(CopyDocumentAsync(myFileName));
    }
    // now that all tasks are running do other things you want to do,
    // wait for all Tasks to finish:
    await Task.WhenAll(runningTasks);
    // now that all tasks are finished you can return
}
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • Task.WhenAll and a nice concise example of how to use it is exactly what I was looking for. The only thing I changed, because in my parent method I couldn't use await, was to change the last line of code to Task.WhenAll(runningTasks).Wait(); – paulH Sep 15 '15 at 14:45