Task, await, dan kapan query benar-benar jalan
Task vs Promise, bahaya .Result dan async void, IEnumerable vs IQueryable, deferred execution
await _db.Products.Where(...).ToListAsync(), bukan .ToList() atau .ToListAsync().Result?"
dalam 60 detik. Satu baris itu memuat dua konsep: async/await dan deferred execution.
1. Task<T> = Promise<T>
Di TS, async function getById(): Promise<Product>. Di C#, async Task<Product> GetByIdAsync().
Task mewakili "the ongoing work"
[1],
sama seperti Promise yang mewakili "the eventual completion (or failure) of an asynchronous operation"
[2].
Task tanpa nilai = Promise<void>.
| C# | TypeScript | |
|---|---|---|
| Tipe | Task, Task<T> | Promise<void>, Promise<T> |
| Sudah jalan saat dipanggil? | Ya, task dari method async selalu "hot" [3] | Ya, "processes that are already happening" [2] |
| Tunggu banyak | await Task.WhenAll(a, b) | await Promise.all([a, b]) |
| Exception | Disimpan di task, dilempar ulang saat await [1] | Promise rejected, dilempar saat await |
| Konvensi nama | Akhiran Async: GetByIdAsync [3] | Tidak ada |
Kenapa backend perlu async? "The await keyword provides a nonblocking way to start a task, then continue execution when the task completes."
[1]
Selama query database berjalan, thread dikembalikan ke pool dan bisa melayani request lain.
[4]
Bagi Anda yang dari Node ini terasa biasa. Bedanya, C# menyediakan dua jalan pintas yang tidak ada di JS, dan keduanya jebakan.
2. Dua jebakan: .Result dan async void
a. .Result / .Wait(): menunggu sambil memblokir
JS tidak punya cara untuk menunggu Promise secara sinkron. C# punya: "The Result property is a blocking property. [...] the thread that's currently active is blocked until the task completes." [5] Panduan ASP.NET Core tegas: "Do not block asynchronous execution by calling Task.Wait or Task<TResult>.Result", karena "Many synchronous blocking calls lead to Thread Pool starvation and degraded response times." Solusinya: "The entire call stack is asynchronous", dari action Controller sampai Repository. [6]
// ❌ memblokir thread request
public IActionResult GetAll() => Ok(_service.GetAllAsync().Result);
// ✅ async dari atas sampai bawah
public async Task<IActionResult> GetAll() => Ok(await _service.GetAllAsync());
b. async void: tidak bisa di-await, exception tidak bisa ditangkap
void sebagai return type async hanya untuk event handler. "For methods other than event handlers that don't return a value, you should return a Task instead,
because an async method that returns void can't be awaited." Lebih parah: "The caller of a void-returning async method can't catch exceptions thrown from the method.
Such unhandled exceptions are likely to cause your application to fail."
[5]
Di ASP.NET Core, Microsoft menyebut "Using async void is ALWAYS a bad practice": request dianggap selesai pada await pertama.
[6]
Padanan Node: memanggil fungsi async tanpa await dan tanpa .catch, lalu mendapat unhandled rejection.
Satu aturan EF Core lagi: "EF Core doesn't support multiple parallel operations being run on the same context instance", jadi selalu await setiap operasi
sebelum memulai yang berikutnya. Jangan Task.WhenAll dua query di atas DbContext yang sama.
[4]
3. Deferred execution: membuat query ≠ menjalankan query
"In LINQ, the execution of the query is distinct from the query itself. In other words, you don't retrieve any data by creating a query variable."
[7]
Operator yang mengembalikan sequence (Where, Select, OrderBy, Skip, Take) bersifat deferred.
Operator yang mengembalikan satu nilai (Count, First, Single, Any, Max) serta ToList/ToArray langsung mengeksekusi.
[7]
Untuk EF Core: "When you call LINQ operators, you're simply building up an in-memory representation of the query. The query is only sent to the database when the results are consumed."
Pemicu yang paling umum: iterasi dengan foreach, atau "an operator such as ToList, ToArray, Single, Count, or the equivalent async overloads".
[8]
var q = _db.Products // IQueryable<Product>: belum ada SQL
.Where(p => p.Stock > 0)
.OrderBy(p => p.Name); // masih belum ada SQL
var list = await q.ToListAsync(); // ⬅ SQL dikirim DI SINI
var ada = await q.AnyAsync(); // ⬅ SQL kedua (query dijalankan ulang)
Itu juga sebabnya tidak ada WhereAsync: Where dan OrderBy "only build up the LINQ expression tree and don't cause the query to be executed in the database.
Only operators which cause query execution have async counterparts."
[4]
Padanan Node: query builder seperti Knex atau Prisma yang belum mengirim apa-apa sampai Anda await, atau generator JS yang baru jalan saat next().
4. IEnumerable<T> vs IQueryable<T>
IQueryable<T> mewarisi IEnumerable<T>.
[7]
Bedanya ada pada apa yang disimpan: query IQueryable "is represented as an expression tree", dan provider-nya menerjemahkan tree itu,
misalnya "translating the expression tree to a query language appropriate for an underlying data source".
[9]
Lambda pada IEnumerable menjadi delegate biasa yang jalan di memori; lambda pada IQueryable menjadi expression tree yang EF Core terjemahkan ke SQL.
[10]
IEnumerable<T> | IQueryable<T> | |
|---|---|---|
| Lambda menjadi | delegate (Func<T,bool>) | expression tree (Expression<Func<T,bool>>) |
| Dijalankan di | memori .NET (LINQ to Objects) | sumber data, lewat provider (SQL Server) |
| Deferred? | Ya | Ya |
| Contoh | List<T>, array, hasil ToList() | DbSet<Product>, _db.Products.Where(...) |
| Padanan Node | array.filter(...) | prisma.product.findMany({ where }) |
Jebakan yang sering ditanyakan: begitu query menjadi IEnumerable, misalnya lewat AsEnumerable(), ToList(), atau Repository yang mengembalikan IEnumerable<Product>,
operator berikutnya jalan di .NET, bukan di database. EF Core menyebut AsEnumerable/ToList sebagai cara "explicitly opt into client evaluation",
dan memperingatkan bahwa "all the data needs to be pulled into memory".
[11]
Aturan praktis: Where, Select, dan paging selagi masih IQueryable, lalu baru ToListAsync().
Microsoft: "Do filter and aggregate LINQ queries [...] so that the filtering is performed by the database."
[6]
// ❌ SELECT * FROM Products, lalu filter di memori
IEnumerable<Product> semua = _db.Products;
var mahal = semua.Where(p => p.Price > 100).ToList();
// ✅ SELECT ... WHERE Price > 100
var mahal = await _db.Products.Where(p => p.Price > 100).ToListAsync();
Latihan 1 — Kapan jalan, apa yang terjadi?
Jawab tanpa scroll ke atas. Soal yang sama akan muncul lagi, diacak, di review campuran 0007.
Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil di review campuran 0007.
Latihan 2 — Ucapkan dengan suara keras
Pasang timer 60 detik dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.
"Kenapa query EF Core di Service Anda ditulisawait _db.Products.Where(...).ToListAsync(), bukan.ToList()atau.ToListAsync().Result?"
Bandingkan dengan jawaban contoh dan checklist-nya
"Where belum menjalankan apa-apa: ia hanya membangun IQueryable, yaitu expression tree. Itu deferred execution, mirip query builder yang baru jalan saat di-await di Node.
Query baru dikirim ke database saat hasilnya dikonsumsi, misalnya lewat ToList, Single, Count, foreach, atau versi async-nya.
Karena masih IQueryable, EF Core menerjemahkan filter ke WHERE di SQL.
ToListAsync dipakai karena I/O database tidak memblokir thread; selama menunggu, thread bisa melayani request lain.
.Result atau .Wait() memblokir thread, dan di ASP.NET Core itu bisa menyebabkan thread pool starvation.
Jadi seluruh call stack saya buat async, dari Controller sampai Repository, mirip async/await di Express."
Checklist: apakah Anda menyebut…
Wherehanya membangunIQueryable/expression tree, belum mengeksekusi (deferred execution)?- query dikirim saat hasil dikonsumsi:
ToList/Single/Count/foreach(atau versi async)? - karena
IQueryable, filter diterjemahkan ke SQL dan dijalankan di database? ToListAsync+awaittidak memblokir thread selama I/O database?.Result/.Wait()memblokir thread dan bisa menyebabkan thread pool starvation?- seluruh call stack async (Controller sampai Repository), dengan padanan Promise/await di Node?
Lulus kalau minimal 4 dari 6. Kalau kurang, ulangi besok pagi sebelum lesson berikutnya (spacing).
Review 0007 juga bisa menanyakan "IEnumerable vs IQueryable" dan "Task vs Promise, .Result, async void" secara lisan, dengan checklist yang sama gayanya.
Ringkasan untuk dicetak: Reference: async/Task dan LINQ · Istilah: GLOSSARY.md
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "apa bedanya Task.WhenAll dengan menjalankan dua query di DbContext yang sama?" atau "kalau Repository saya mengembalikan IQueryable, apa untung-ruginya?"
Sumber: [1] Asynchronous programming (C#) · [2] MDN: Promise · [3] Task-based Asynchronous Pattern · [4] EF Core: Asynchronous Programming · [5] Async return types · [6] ASP.NET Core Best Practices · [7] Introduction to LINQ Queries · [8] EF Core: How Queries Work · [9] IQueryable<T> · [10] Expression Trees · [11] EF Core: Client vs. Server Evaluation