Skip to content

Instantly share code, notes, and snippets.

@idg10
Last active September 19, 2018 09:55
Show Gist options
  • Save idg10/85760956b44c254b432e9aa69941ecf0 to your computer and use it in GitHub Desktop.
Save idg10/85760956b44c254b432e9aa69941ecf0 to your computer and use it in GitHub Desktop.
Resource handling with combined enumerable and Task-based deferred execution, ensuring correct sequencing
public static IEnumerable<Task<int>> ParseFile(string path)
{
using (var reader = new StreamReader(path))
{
while (!reader.EndOfStream)
{
Task<int> readLineTask = null;
try
{
readLineTask = ReadAndParseAsync(reader);
yield return readLineTask;
}
finally
{
// There are three ways we can end up here:
// 1) an exception occurred (in which case readLineTask may still be null)
// 2) The caller asked for the next item (in which case execution will resume
// from the yield return, and we'll try another loop iteration
// 3) The caller has decided to stop early, calling Dispose on the enumerator
// before reaching the end
// If it's either 2 or 3 (i.e., we have a non-null readLineTask), we need to ensure
// that the last task we created is complete. (In case 2, we need it to be complete
// because we can't test for EndOfStream, nor can we go on to begin another read until
// the last read finishes. In case 3, we need to ensure that the read completes before
// we dispose the StreamReader.)
// Hopefully the caller already awaited the task for us. But if they didn't we need to
// block now until it completes.
if (readLineTask?.IsCompleted == false)
{
// Calling Wait is almost always a terrible idea. But we've been
// forced into this by the mismatch between our return type, and the
// nature of the work we're doing.
readLineTask.Wait();
}
}
}
}
}
private static async Task<int> ReadAndParseAsync(StreamReader reader)
{
string line = await reader.ReadLineAsync().ConfigureAwait(false);
return int.Parse(line);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment