I need to delete a file when an error occurs during loading it.
In other words:
Happy path:
Unhappy path:
- Load a file, exception is thrown
- Delete the file
- Move on
Exceptional path:
- Load a file, exception is thrown
- Delete the file, exception is thrown
- Handle exception, move on
From these points, it appears we'd need a task that loads a file. If that task fails, we need to try to handle the unhappy path gracefully.
I haven't written async code with async/await, but I'd probably get away with this using a Task
that loads the file, and then decide whether I'd .ContinueWith
another Task
that deletes the file, and then decide how to handle the exceptional path where that other task would have failed.
Signature
public async Task LoadFromStorageFile(string fileName)
This StackOverflow answer refers to an authoritative source for async method naming conventions:
The Task-based Asynchronous Pattern (TAP) dictates that methods should
always return a Task<T>
(or Task
) and be named with an Async suffix;
this is separate from the use of async
. Certainly, Task<bool> Connect()
(or async Task<bool> Connect()
) will compile and run just
fine, but you won't be following the TAP naming convention.
The signature for Task LoadFromStorageFile(string)
would follow the naming convention for the pattern, by appending "Async" to the method's name:
public async Task LoadFromStorageFileAsync(string fileName)
Async/Await Exception Handling
As I've found in this StackOverflow answer, an exception thrown in an async method will bubble up to the caller, so whoever called await LoadFromStorageFileAsync("somefile.txt");
can know whether to move on, or to handle the unhappy path.
It is not allowed to perform async operations in a catch block.
Indeed. There's a question on Stack Overflow, with a nice answer from one of our top reviewers here on CR, that shows how that limitation can be circumvented. In a nutshell:
bool loadSucceeded;
try
{
var folder = await ApplicationHelper.GetApplicationSaveFolder();
file = await folder.GetFileAsync(fileName);
// ...
loadSucceeded = true;
}
catch
{
loadSucceeded = false;
}
LoadFromStorageFileAsync
can then look like this:
public async Task LoadFromStorageFileAsync(string fileName)
{
bool loadSucceeded;
StorageFile file;
try
{
// happy path
var folder = await ApplicationHelper.GetApplicationSaveFolder();
file = await folder.GetFileAsync(fileName);
// ...
loadSucceeded = true;
}
catch(Exception exception)
{
// log exception
loadSucceeded = false;
}
if (!loadSucceeded && file != null)
{
try
{
// unhappy path
await file.DeleteAsync(StorageDeleteOption.PermanentDelete);
deleteSucceeded = true;
}
catch(Exception exception)
{
// exceptional path
// log exception
}
}
}
This code doesn't rethrow whatever exception caused loadSucceeded
to be false
- rethrowing that exception in the unhappy path makes no sense, as was said before, that finally
block is not needed. Instead, you could catch(Exception exception)
(or anything more specific) in the happy path's catch
block, and write a trace, a debug, or a warning log entry with the exception and its stack trace. At that point, it's taken care of, there's no need to re-throw it.