Doing it a slightly different way I see the data has to be collected first and after the thread has closed, only then can I perform extract actions on the actual data. Still nooooot quite understanding why (aka your description Roi) but I will get there.
C#:
public async Task<IEnumerable<Student>> GetStudents()
{
using (IDbConnection db = Conn)
{
db.Open();
var results = await db.QueryAsync<Student>("spGetStudents", commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
Async / Await
await basically saves the state at the point its encountered; and essentially packages up the remainder of the code in a continuation block, similar to what can be done with
Task's ContinueWith method.
await syntactically makes it easy to:
- enable concurrency,
- save the state,
- link a continuation block of code
- seamlessly unwrap the value held onto by the
Task.
i.e. your
results variable inside your function is an
IEnumerable<...> and not a
Task<IEnumerable<...>> .
IEnumerable
IEnumerable as I implied is a transitional container type that wraps a value so that transformation can be performed on the value using functional algebras like
Select,
Where,
SelectMany, etc.
Note:
In a language like Haskell with HKTs there would be no reason to transition from List to IEnumerable and back to List -- because HKTs would allow List to have the same functional power as IEnumerable without having to duplicate all the code it took to enable that functionality for IEnumerable. Think of it in a similar way to how an Abstract class can be used to share code between related classes. Only its more powerful.
IEnumerable's Lazy Computation
It appears however that either you want the result to be either a
List<Student> instead of
IEnumerable<Student> as your function's signature implies; but you're shooting yourself in the foot with an implicit type conversion from
List<Student> back to
IEnumerable<Student> -- the
results variable is of type
IEnumerable<Student>, which you then convert to
List<Student>, and the function's signature
Task<IENumerable<Student>> then implicitly converts that back to
IEnumerable<Student>.
As I said earlier
IEnumerable is designed for Lazy computation as opposed to C#'s default Eager computation. The reason is to help programmer's avoid shooting themselves in the foot with unexpected computation costs.
Essentially all transformations using
IEnumerable are composed together without incurring any compute cost when method calls like
.Select(x => ....) or
.Where(x => ...) are chained together. A Lazy computation doesn't compute the result until you ask it to; e.g. using a
ToList() method call.
Here's a simple example that will demonstrate this.
C#:
namespace LazyExample {
class Program {
public static int Increment(int x) {
Console.WriteLine("Incrementing");
return x + 1;
}
public static async Task<IEnumerable<int>> Blah() {
var result = await Task.Run(() => {
return Enumerable.Empty<int>().Append(1).Select(Increment);
});
return result;
}
static async Task Main(string[] args) {
var output = await Blah();
}
}
When you run this code, you will see no output; that's because the
output variable has received the Lazy
IEnumerable.
If I change the code to something similar to yours:
i.e. Adding the
ToList() method call.
C#:
namespace LazyExample {
class Program {
public static int Increment(int x) {
Console.WriteLine("Incrementing");
return x + 1;
}
public static async Task<IEnumerable<int>> Blah() {
var result = await Task.Run(() => {
return Enumerable.Empty<int>().Append(1).Select(Increment);
});
return result.ToList();
}
static async Task Main(string[] args) {
var output = await Blah();
}
}
When you run this example; you will see the word "Increment" in your console; because the
ToList method call forces computation of the lazy computation held onto by
IEnumerable. This lazy behaviour is beneficial not only because it avoids costly
Big O mistakes by programmers, but equally important it allows the programmer to decide when to incur the cost of compute.
As for your example; I'd suggest either holding onto the Laziness i.e. remove the
ToList() and do that later in your code; or change your function's signature to
Task<List<Student>> to avoid the implicit and unnecessary conversion back to
IEnumerable<Student>.