المفاهيم
هنفهم إيه؟ بتستنى database أو خدمة دفع: إزاي تسيب التطبيق يخدم طلبات تانية أثناء الانتظار، وإزاي تمنع تعارض شغل متزامن؟ هنفرّق بين async وparallelism، ونستخدم الإلغاء والمزامنة لما يكون فيه سبب حقيقي.
قبل ما تبدأ: التنفيذ المتزامن يمشي خطوة ثم ينتظر انتهاءها قبل الخطوة التالية. في C#، Task بيمثل عملية أو نتيجة قد تكتمل لاحقاً؛ وجوده لوحده مش معناه thread جديد. ابدأ بكارت المطعم، وبعده افهم Task قبل وlock.
المصطلحات اللي تحتها خط منقّط تقدر تضغط عليها لمعرفة معناها، وترجع تكمل من نفس مكانك.
#Sync و Async: مثال الجرسون في المطعمSync vs async: the restaurant analogy
الجرسون (waiter) الـ Sync: بياخد الأوردر، ويروح المطبخ، ويقف مستني لحد ما الأكل يخلص، وبعدين يرجع. وطول الوقت ده مش بيخدم حد تاني. فلو عندك 10 زباين، محتاج 10 جرسونات.
الجرسون الـ Async: بياخد الأوردر، ويديه للمطبخ، ويروح يخدم ترابيزة تانية. ولما الأكل يجهز، المطبخ بينادي، وأي جرسون فاضي يوصّله. فجرسون أو اتنين يخدموا 10 ترابيزات.
الجرسون هو الـ thread، والمطبخ هو الـ (database، و ، و files). والـ async مش بيخلّي المطبخ أسرع، بيخلّي الجرسونات مايضيعوش وقتهم مستنيين.
public IActionResult Get(int id)
{
var order = db.Orders.Find(id); // thread blocked ~20 ms
var rate = http.GetStringAsync(ratesUrl).GetAwaiter().GetResult(); // blocks
return Ok(order);
}public async Task<IActionResult> Get(int id, CancellationToken ct)
{
var order = await db.Orders.FindAsync([id], ct); // thread released while the DB works
var rate = await http.GetStringAsync(ratesUrl, ct);
return Ok(order);
}الفايدة في : نفس عدد الـ threads يستحمل requests أكتر بكتير. ودي scalability مش speed: الـ request الواحد مش بيبقى أسرع.
#async و await بيشتغلوا إزاي من جوّهHow async/await works under the hood
- طلب محتاج سعر شحن من خدمة خارجية. إرسال الطلب يستغرق وقتًا قصيرًا، لكن الرد قد يصل لاحقًا؛ أغلب المدة انتظار وليست حسابات.
- عند await لمهمة غير مكتملة، الدالة تحفظ ما تحتاجه لتكمل ثم تعيد التحكم للمستدعي. عند اكتمال المهمة يستكمل التنفيذ من بعد await.
- الفائدة إن thread لا يلزم يظل محجوزًا طول انتظار . await لا يخلق thread جديدًا تلقائيًا، والمهمة المكتملة قد تسمح باستمرار التنفيذ مباشرةً.
الـ compiler بيحوّل الـ method الـ async لـ state machine (class فيه MoveNext() وحالة بتقول وصلنا لأنهي await).
- الـ method بتشتغل عادي (synchronously) لحد أول
await. - لو الـ task اللي بتستناه خلص فعلاً (زي cache hit)، بتكمّل على طول من غير ما تسيب الـ thread.
- لو لسه ماخلصش، بتسجّل continuation ("كمّل من هنا لما يخلص")، وترجّع
Taskمش مكتمل للي ناداها، والـ thread بيتحرر. - مع network I/O الـ async الحقيقي، مفيش thread مستني: الـ بيبلّغ لما الداتا توصل. بعض async للملفات أو providers ممكن تشغّل الشغل الـ blocking على thread.
- لما العملية تخلص، الـ continuation بيتنفذ على thread من الـ ThreadPool (أو على الـ thread في تطبيقات الـ desktop)، ويكمّل من نفس النقطة بكل المتغيرات.
- لو حصل exception، بيتخزن جوه الـ Task، ويترمي تاني لما تعمل
await.
public async Task<decimal> GetTotalAsync(Guid orderId, CancellationToken ct)
{
var order = await repo.GetAsync(orderId, ct); // may suspend here
var rate = await fx.GetRateAsync(order.Currency, ct); // and here
return order.Total * rate; // resumes with locals intact
}
#Thread و Task و ThreadPoolThread vs Task vs ThreadPool
| Thread | Task | |
|---|---|---|
| هو إيه | thread حقيقي في الـ ، وليه stack خاص (حوالي 1 MB) | وعد بنتيجة لعملية هتخلص بعدين |
| التكلفة | غالي في الإنشاء والـ memory | رخيص، والشغل بيستخدم threads من الـ ThreadPool |
| النتيجة | مفيش return value مباشر | Task<T> بيرجّع قيمة |
| Exceptions | لو مااتمسكتش بتوقّع الـ process كله | بتتخزن وتطلع عند الـ await |
| الإلغاء والتركيب | يدوي | CancellationToken و WhenAll و WhenAny |
| وقت انتظار network الحقيقي | واقف (blocked) | ممكن ميبقاش فيه thread خالص |
الـ ThreadPool: مجموعة threads جاهزة بيعيد استخدامها الـ runtime. والـ بيخدم كل request على thread منها. ولو عملت block للـ threads دي (.Result)، الـ pool بيخلص، ودي اسمها thread pool starvation.
#Task.Run و WhenAll و WhenAnyTask.Run, WhenAll and WhenAny
Task.Run بيحط شغل على thread من الـ pool. مفيد في الـ والـ console للشغل التقيل على الـ . أما في فإنت أصلاً على thread من الـ pool، فلفّ الـ بيه overhead بس.
var order = await Task.Run(() => db.Orders.Find(id)); // still blocks a pool threadvar order = await db.Orders.FindAsync([id], ct);// WhenAll: start everything first, then wait for ALL → total time ≈ the slowest call
var stockTask = inventory.GetStockAsync(sku, ct);
var priceTask = pricing.GetPriceAsync(sku, ct);
var reviewsTask = reviews.GetAsync(sku, ct);
await Task.WhenAll(stockTask, priceTask, reviewsTask); // (1)
var page = new ProductPage( // (2) all complete
await stockTask, await priceTask, await reviewsTask);
// WhenAny: continue when the FIRST task finishes
var winner = await Task.WhenAny(primary.GetAsync(ct), replica.GetAsync(ct));
var data = await winner; // (3) observe result or exception
// WaitAsync (.NET 6+) times out the wait; the CTS times out the operation
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(5));
var receipt = await gateway.ChargeAsync(order, timeoutCts.Token) // (4)
.WaitAsync(TimeSpan.FromSeconds(5), ct);
- (1) التلات calls بيبدأوا مع بعض. لو كل واحد 100ms، الإجمالي حوالي 100ms مش 300ms. ولو واحد فشل، الـ
awaitبيرمي أول exception، والباقيين فيException.InnerExceptions. - (2) الـ tasks خلصت فعلاً، فالـ
awaitهنا بيرجّع على طول. - (3)
WhenAnyبيرجّع الـ task اللي خلص الأول، ومش بيرمي exception، فلازم تعملawaitللفايز. - (4)
WaitAsyncبيوقف الانتظار بعد 5 ثواني بس. إلغاء العملية محتاج timedCancellationTokenSourceيتبعت للعملية، زيtimeoutCts.Tokenهنا؛ والـ provider ممكن ما يحترمش الإلغاء.
WhenAll على queries على نفس الـ . الـ DbContext مش thread-safe وهيرمي exception. استخدم IDbContextFactory واعمل context لكل task، أو نفّذهم ورا بعض.#CancellationTokenCancellation
المستخدم ممكن يقفل صفحة التقرير، أو الـ request ينتهي قبل ما قاعدة البيانات تخلص. بدل ما العملية تفضل شغالة بلا داعي، caller يمرر إشارة إلغاء، وكل طبقة تختار كيف تستجيب لها. الكود يوضح تمرير الإشارة وفحصها؛ الإلغاء تعاوني ومش بيقتل thread بالقوة.
[HttpGet("report")]
// (1) ct is cancelled when the client disconnects
public async Task<IActionResult> Report(CancellationToken ct)
{
// (2) pass it everywhere
var rows = await db.Sales.Where(s => s.Year == 2026).ToListAsync(ct);
foreach (var row in rows)
{
ct.ThrowIfCancellationRequested(); // (3) check inside long CPU loops
Process(row);
}
return Ok();
}
// combine the request token with your own timeout
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(TimeSpan.FromSeconds(10)); // (4)
await partnerApi.SyncAsync(cts.Token);
- (1) بيديك بيتلغي لو العميل قفل الصفحة، فمفيش داعي تكمّل تقرير تقيل.
- (2) مرّره لكل async call. والـ بيمرره للـ database provider، بس مش كل provider بيحترم الإلغاء.
- (3) في الـ loops الطويلة، اتشيك بنفسك.
- (4) Linked token: بيتلغي لو العميل مشي أو عدّت 10 ثواني.
الإلغاء تعاوني (cooperative): الـ token مش بيوقف الكود بالعافية، الكود هو اللي بيشيك عليه. ولما يتلغي بيترمي OperationCanceledException، وده مش error، فماتسجّلوش كـ error في الـ logs. EF Core بيمرر token للـ database provider، لكن مش مضمون إن كل provider ينفّذ الإلغاء.
CancellationToken.None للخطوات اللي لازم تكمل، وخلّي فيه reconciliation.#Race conditions و lock و SemaphoreSlim و InterlockedRace conditions and synchronization primitives
- المخزون قطعة واحدة. طلب A قرأ 1، وبعده طلب B قرأ 1 قبل ما A يحفظ. كل واحد قرر إن البيع مسموح؛ القراءة ثم القرار ثم الكتابة كانت سلسلة غير محمية.
- داخل عملية واحدة ممكن تحمي الحالة المشتركة بقفل مناسب. لكن لو عندك نسختان من السيرفر، قفل الذاكرة في النسخة الأولى لا يحمي الثانية.
- للمخزون المخزن في قاعدة البيانات، مثال الحل تعديل مشروط ذري: أنقص الكمية فقط إذا كانت أكبر من صفر ثم تحقق من عدد الصفوف المتأثرة. حدد مكان مشاركة الحالة قبل اختيار الأداة.
اتنين بيسحبوا من نفس الحساب في نفس اللحظة: الاتنين قروا الرصيد 100، والاتنين سحبوا 80، والاتنين كتبوا 20. فاتسحب 160 من 100! وده بيحصل لما أكتر من thread يقرا ويعدّل نفس الداتا من غير تنظيم.
private int _count;
// NOT atomic: read → add → write, so concurrent updates get lost
public void Hit() => _count++;
// 1) lock: one thread at a time (synchronous code only)
// .NET 9+ Lock type (before: private readonly object _gate = new();)
private readonly Lock _gate = new();
public void Add(Item i) { lock (_gate) { _items.Add(i); _total += i.Price; } }
// 2) Interlocked: atomic operations on a single value
Interlocked.Increment(ref _count);
// 3) SemaphoreSlim: works with await, and can allow N at a time
private readonly SemaphoreSlim _refresh = new(1, 1);
public async Task RefreshTokenAsync(CancellationToken ct)
{
await _refresh.WaitAsync(ct);
try { _token = await auth.GetTokenAsync(ct); }
finally { _refresh.Release(); } // always release in finally
}
| الأداة | إمتى |
|---|---|
| lock / Lock | block قصير sync. وماينفعش await جوه lock، والـ compiler بيمنعك. |
| Interlocked | عداد أو flag واحد، وهو الأسرع. |
| SemaphoreSlim | تحمي كود async، أو تحدد عدد العمليات المتوازية (throttling). |
| ConcurrentDictionary / ConcurrentQueue | collections آمنة جاهزة. |
| Channel<T> | producer و consumer async في الـ memory. |
lock مش هيحمي حاجة بينهم. وللداتا المشتركة: الـ database (concurrency ، أو UPDATE ... WHERE Stock >= @qty، أو row lock)، أو distributed lock.#Deadlocks وأشهر أخطاء الـ asyncDeadlocks and common async mistakes
.Resultو.Wait()(sync over async): بيعمل block للـ thread. وفي الـ أو ASP.NET القديم بيعمل : الـ thread مستني الـ task، والـ task مستني يرجع على نفس الـ thread. وفي مفيش SynchronizationContext، فمفيش deadlock، بس فيه thread pool starvation.async void: مفيش Task ترجع، فمحدش يقدر يعمل await، والـ exception بتوقّع الـ process. استخدمه في الـ event handlers بس.- نسيان
await: العملية بتشتغل fire-and-forget، والـ exceptions بتضيع، والـ ممكن يتعمله dispose قبل ما تخلص. - Fire-and-forget جوه request:
_ = SendEmailAsync()بيكمّل بعد ما الـ scope يتقفل. استخدم queue أو background job أو . - عمليتين مع بعض على نفس الـ DbContext.
lockحوالينawait: مش مسموح، استخدمSemaphoreSlim.- عدم تمرير الـ CancellationToken.
ConfigureAwait(false): بيقول للـ await "مش محتاج ترجع على نفس الـ context". مهم في الـ libraries والـ UI. وفي كود ASP.NET Core نفسه مالوش تأثير، لأن مفيش SynchronizationContext.
Deadlock في الـ locks: thread 1 ماسك A ومستني B، و thread 2 ماسك B ومستني A، فالاتنين واقفين للأبد. والحل ترتيب ثابت للـ locks، و critical sections قصيرة، و timeouts. ونفس الكلام في الـ database .
#Parallel و async و ChannelsParallelism vs asynchrony, and Channels
| Async (concurrency) | Parallel (parallelism) | |
|---|---|---|
| الهدف | ماتضيعش threads وإنت مستني | تستخدم كذا core مع بعض |
| نوع الشغل | -bound | -bound |
| الأدوات | async و await و WhenAll | Parallel.For و Parallel.ForEachAsync و |
// CPU-bound: spread across cores
Parallel.For(0, images.Length, i => images[i] = Resize(images[i]));
// I/O-bound with a concurrency limit (.NET 6+)
var options = new ParallelOptions { MaxDegreeOfParallelism = 8, CancellationToken = ct };
await Parallel.ForEachAsync(skus, options,
async (sku, token) => await SyncStockAsync(sku, token));
// Channel: in-memory async queue; bounded = back-pressure when consumers are slow
var channel = Channel.CreateBounded<OrderPlaced>(capacity: 1000);
await channel.Writer.WriteAsync(evt, ct); // producer
await foreach (var e in channel.Reader.ReadAllAsync(ct)) await HandleAsync(e); // consumer
جملة تفتكرها: "Concurrency is about dealing with many things at once; parallelism is about doing many things at once."
أسئلة الانترفيو
مفيش نتايج
راجع فهمك
ليه الاختيار ده مناسب؟ إمتى ممكن ماينفعش؟ وإيه اللي هيتغير لو الحمل زاد أو ظهر فشل؟
4.1What is the difference between synchronous and asynchronous programming?Junior
الـ Sync بيوقف الـ thread لحد ما العملية تخلص. والـ Async بيحرر الـ thread وقت الانتظار ويكمّل لما النتيجة تيجي. وفي الـ الفايدة scalability مش سرعة الـ request الواحد.
Synchronous code blocks the thread until an operation completes. Asynchronous code starts the operation, releases the thread while waiting, and continues when the result arrives. In a web that lets the same thread pool handle many more concurrent requests during , so it improves and scalability rather than the speed of a single request.
4.2How do async and await work under the hood?Mid
الـ compiler بيحوّل الـ method لـ state machine. وعند كل await على task لسه ماخلصش، بيسجّل continuation ويرجّع Task غير مكتمل، فالـ thread بيتحرر. ولما العملية تخلص، بيكمّل من نفس النقطة على thread من الـ pool.
The compiler rewrites an async method into a state machine. At each await, if the task isn't complete, the method registers a continuation and returns an incomplete Task to its caller, freeing the thread. During no thread waits; the signals completion. The continuation then resumes the state machine at the next step, on a thread-pool thread in or on the captured context in apps. Exceptions are stored in the task and rethrown at the await.
4.3What is the difference between a Task and a Thread?Junior
الـ Thread هو thread غالي. أما الـ Task فهو وعد بنتيجة: رخيص، وبيستخدم الـ ThreadPool، وبيدعم return value و exceptions و cancellation والتركيب.
A thread is an operating-system thread with its own stack, expensive to create. A Task is a higher-level representation of an asynchronous operation and its eventual result. work in tasks runs on pooled threads, and tasks don't hold a thread while waiting. Tasks also give you return values, exception propagation, cancellation and composition, so modern code uses tasks rather than raw threads.
4.4What is Task.Run and when should you use it?Mid
بيحط شغل على thread من الـ pool. مفيد في الـ والـ console. أما في ، فماتلفّش بيه علشان "يبقى async"، لأنه بينقل الـ blocking لـ thread تاني بس.
Task.Run queues a to the thread pool. I use it to offload -bound work from a thread, or to run independent CPU-heavy pieces in parallel. In I don't wrap synchronous in Task.Run to make it look async; that just moves the blocking to another pool thread. I use real async instead.
4.5What is the difference between Task.WhenAll and Task.WhenAny?Mid
الـ WhenAll بيكمّل لما كلهم يخلصوا، والوقت على قد أبطأ واحد. والـ WhenAny بيكمّل مع أول واحد يخلص، وبيرجّع الـ task نفسه، ومفيد للـ timeouts أو أسرع رد.
Task.WhenAll completes when all tasks complete, so independent operations take about as long as the slowest one; awaiting it rethrows the first exception while the task's Exception holds all of them. Task.WhenAny completes when the first task completes and returns that task without throwing, so you await it to get its result. I use WhenAll to run independent calls concurrently, and WaitAsync or WhenAny for timeouts.
4.6What are the most common async/await mistakes?Mid
.Result و .Wait()، و async void، ونسيان await، و fire-and-forget جوه request، وعمليتين على نفس الـ ، و lock حوالين await، وعدم تمرير الـ CancellationToken.
Blocking on tasks with .Result or .Wait, which under a synchronization context and starves the thread pool in ; async void outside event handlers; forgetting to await, which silently drops exceptions; fire-and-forget work that outlives the request's scope; concurrent operations on one ; trying to lock across an await; and not passing the CancellationToken through.
4.7Why is async void dangerous?Junior
مفيش Task، فمحدش يقدر يعمل await أو يعرف إمتى خلصت، والـ exception مش بتتمسك وبتوقّع الـ process. مسموح بيه في الـ event handlers بس.
An async void method returns nothing awaitable, so callers can't wait for it or observe failures. Its exceptions are raised directly on the synchronization context or thread pool and usually crash the process. It exists only for event handlers; everything else should return Task or ValueTask.
4.8What is the difference between parallel and asynchronous programming?Mid
الـ Async: ماتعملش block وإنت مستني . والـ Parallel: تستخدم كذا core مع بعض لشغل . وتقدر تجمعهم بـ Parallel.ForEachAsync.
Asynchronous programming is about not blocking while waiting, mostly on , and can run on very few threads. Parallel programming splits -bound work across cores to finish sooner, with Parallel.For, or multiple tasks. They combine well, for example Parallel.ForEachAsync processes many I/O calls with a concurrency limit.
4.9What is a CancellationToken and how do you use it?Mid
إشارة إلغاء تعاونية. بيديك واحد مع كل request، ومرّره لكل async call. وللـ timeouts، اعمل linked source ومعاه CancelAfter.
It's a cooperative cancellation signal created by a CancellationTokenSource. Code receiving it checks it or passes it to that support it, which throw OperationCanceledException when it fires. In , actions receive a that cancels when the client disconnects, and I flow it through and HttpClient calls. For timeouts I create a linked source with CancelAfter.
4.10What does ConfigureAwait(false) do, and do you need it in ASP.NET Core?Senior
بيقول إن الـ continuation مش لازم يرجع للـ SynchronizationContext الأصلي. مهم في الـ libraries والـ . وفي كود نفسه مالوش تأثير.
It tells the awaiter not to resume on the captured synchronization context. In apps and classic ASP.NET that avoids and needless context switches, so library code uses it everywhere. has no synchronization context, so in application code it makes no difference, though libraries still use it because they don't know where they'll run.
4.11Task vs ValueTask: when would you use ValueTask?Senior
الـ ValueTask struct، فلو الـ method غالباً بترجع synchronously (زي cache hit) مفيش . وليه قيود: ماتعملهوش await مرتين، ولا تعمل block عليه قبل ما يخلص. فالافتراضي Task، و ValueTask في الـ hot paths بس.
ValueTask is a struct that avoids allocating a Task when a method usually completes synchronously, such as returning a cached value. It has rules: await it only once and don't block on it before it completes. I default to Task and use ValueTask on hot paths where profiling shows the matter.
4.12What is a race condition and how do you prevent it?Mid
أكتر من thread بيعدّلوا نفس الداتا من غير تنظيم، فالنتيجة بتعتمد على التوقيت. والحل ، أو lock، أو Interlocked، أو SemaphoreSlim، أو concurrent collections. وبين الـ instances: الـ database.
A is when the outcome depends on the timing of threads touching shared mutable state, such as two threads incrementing a counter and losing an update. I avoid shared mutable state where possible, use data, or synchronize with lock, Interlocked, SemaphoreSlim or concurrent collections. Across multiple service instances, I enforce correctness in the database with , atomic conditional updates or row locks.
4.13How does the lock keyword work?Mid
بيتحوّل لـ Monitor.Enter و Monitor.Exit جوه try/finally، أو لـ Lock.EnterScope() لو بتستخدم نوع Lock في 9 وما بعده. واقفل على object private readonly، مش this ولا string، ومفيش await جوه.
lock ensures only one thread at a time runs the block and always releases it, compiling to Monitor.Enter and Exit in try/finally, or to Lock.EnterScope when you lock on the System.Threading.Lock type introduced in 9. I lock on a private readonly object, never on this, a type or a string, keep the section short, and never await inside it; for async code I use SemaphoreSlim.
4.14What is a deadlock and how do you avoid it?Mid
اتنين (أو أكتر) كل واحد ماسك مورد ومستني التاني للأبد. والحل ترتيب ثابت للـ locks، و critical sections قصيرة، و timeouts، وماتعملش sync over async. وفي الـ : قصيرة، و retry للـ victim.
A happens when two or more threads or each hold a resource and wait for one held by another, so none can proceed. I avoid it by acquiring locks in a consistent order, keeping critical sections and transactions short, using timeouts, and never blocking on async code. Databases detect deadlocks and kill one transaction as the victim, so those operations should be retried.
4.15What is thread pool starvation and how do you detect it?Senior
كل threads الـ pool واقفة (blocked)، غالباً بسبب .Result، والـ pool بيزوّد threads ببطء. فالـ بتطلع جداً مع إن الـ فاضي. والاكتشاف بـ dotnet-counters (ThreadPool Queue Length بيطلع). والحل async بالكامل.
It happens when pool threads are blocked, typically by sync-over-async code, so queued work waits while the pool slowly injects new threads. explodes while stays low. dotnet-counters shows a growing thread-pool queue length and thread count. The fix is making the call chain async all the way down, not raising the minimum thread count.
4.16What are concurrent collections, and what is a Channel?Mid
collections آمنة لأكتر من thread: ConcurrentDictionary و ConcurrentQueue و ConcurrentBag. وخد بالك إن GetOrAdd ممكن ينده الـ factory أكتر من مرة. أما الـ Channel<T> فـ queue async للـ producer والـ consumer، ولو bounded بيعمل back-pressure.
Concurrent collections like ConcurrentDictionary and ConcurrentQueue are thread-safe without manual locking. A subtle point: GetOrAdd may run its factory more than once under contention, so for one-time creation I store Lazy values. Channel of T is an async producer-consumer queue; a bounded channel applies back-pressure when consumers fall behind, which makes it ideal for in-process pipelines and background workers.
4.17What is the difference between IEnumerable and IAsyncEnumerable?Mid
الـ IAsyncEnumerable<T> بيطلّع العناصر واحد واحد ومعاها await (await foreach). مفيد للـ streaming من الـ أو الـ من غير ما تحمّل كله في الـ memory.
IAsyncEnumerable produces and consumes a sequence asynchronously, awaiting between items with await foreach and async iterators. It's useful for streaming large query results from or returning a streamed array from an endpoint without loading everything into memory or blocking threads.
اختبر نفسك
إجابات الاختبار وعلامات إكمال المحاور بتتحفظ على المتصفح ده، ومش بتتنقل لأجهزة تانية.
What is the main benefit of async/await in an ASP.NET Core API?
Calling .Result on a Task inside ASP.NET Core mainly causes...
Three independent 100 ms calls awaited with Task.WhenAll take about...
You need to await inside a critical section. Which primitive?
await جوه lock. والـ SemaphoreSlim بيدعم WaitAsync.A C# lock protects a stock counter, but the API runs on 4 instances. Is stock safe?
UPDATE ... WHERE Stock >= @qty.Two EF Core queries on the same DbContext are started with Task.WhenAll. What happens?
Where is async void acceptable?
Best tool for CPU-bound work across cores?