Tag: system design

  • I Thought I Understood async/await… Until I Asked One Simple Question

    I Thought I Understood async/await… Until I Asked One Simple Question

    A few days ago, I came across a post explaining async and await in C#. It talked about state machines, ThreadPool, asynchronous operations, and methods resuming after await.

    I read the entire post.

    Honestly…

    I understood almost nothing.

    Like many developers, I had a few assumptions in my head:

    • Does async create a new thread?
    • Does await stop the thread?
    • If the method pauses, what exactly is paused?
    • When execution resumes, why doesn’t it immediately execute the next line?
    • If another thread continues the work, how does the program know where to continue?

    The more I thought about it, the more confused I became.

    So I started asking questions.

    The first realization completely changed my understanding:

    async does not create a new thread.

    That surprised me.

    For years, I subconsciously associated async with “background thread.”

    It isn’t.

    async simply tells the compiler that the method contains asynchronous operations and can be paused and resumed.

    Then came the second realization:

    await pauses the method, not the thread.

    That sentence looks simple, but it took me a while to truly understand it.

    Imagine an ASP.NET Core API.

    A request arrives, and a ThreadPool thread starts executing your controller action.

    Eventually, the code reaches this line:

    await _repository.GetCustomerAsync();
    

    The database now needs a few seconds to respond.

    My original assumption was that the thread would just sit there waiting.

    Wrong.

    The thread is released back to the ThreadPool so it can serve another request.

    Your method remembers exactly where it stopped.

    When the database finishes, .NET picks an available thread and continues executing the method from the line after await.

    Another question immediately came to my mind.

    “If execution continues after await, shouldn’t the next line execute immediately?”

    Then I realized something I had completely overlooked.

    Consider this example:

    public async Task SaveCustomerAsync()
    {
        Console.WriteLine("Step 1");
    
        await SaveToDatabaseAsync();
    
        Console.WriteLine("Step 2");
    }
    
    public async Task SaveToDatabaseAsync()
    {
        await Task.Delay(5000);
    
        Console.WriteLine("Database Saved");
    }
    

    Initially, I expected the output to be:

    Step 1
    Step 2
    Database Saved
    

    But that’s not what happens.

    The actual output is:

    Step 1
    Database Saved
    Step 2
    

    Why?

    Because SaveCustomerAsync() isn’t waiting for Task.Delay().

    It’s waiting for the entire SaveToDatabaseAsync() method to finish.

    Only after that method completes does execution continue after its own await.

    That was my “aha!” moment.

    Finally, I understood the role of threads.

    Threads don’t travel with your method.

    They simply execute whatever work is ready.

    When an await is reached:

    • The method pauses.
    • The current thread is released.
    • The asynchronous operation continues elsewhere (such as the database or operating system).
    • When the operation finishes, an available ThreadPool thread resumes the method.

    No thread sits idle.

    No CPU cycles are wasted waiting.

    That’s why async/await improves the scalability of applications like ASP.NET Core APIs.

    Looking back, I realized something important.

    Understanding async/await isn’t about memorizing definitions.

    It’s about asking the right questions.

    Sometimes the biggest breakthrough comes from admitting:

    “I don’t actually understand this yet.”

    And that’s perfectly okay.

    Every experienced developer has been there.

    If this post clears up the same confusion I had, then sharing my learning journey was worth it.

  • User বারবার Send Button চাপল, আর Customer-এর টাকা দুইবার কেটে গেল! এটা কীভাবে প্রতিরোধ করবেন?

    User বারবার Send Button চাপল, আর Customer-এর টাকা দুইবার কেটে গেল! এটা কীভাবে প্রতিরোধ করবেন?

    ধরুন, আপনি একটি Payment API তৈরি করেছেন।

    একজন User “Pay Now” বাটনে চাপ দিলেন।

    কিন্তু Server একটু ধীর (Slow)।

    User ভাবলেন,

    “হয়তো Click হয়নি!”

    তিনি আবার Button চাপলেন।

    তারপর আবার।

    কয়েক সেকেন্ড পরে…

    Customer Support থেকে ফোন—

    “একই Payment-এর জন্য Customer-এর টাকা দুইবার কেটে গেছে!”

    এখন প্রশ্ন হলো—

    সমস্যাটা User-এর, নাকি System-এর?

    আমার মতে, System-এর।

    কারণ একটি ভালো Payment System কখনোই একই Payment একাধিকবার Process করবে না।


    প্রথম প্রতিরক্ষা: Frontend

    Frontend থেকেই Duplicate Request কমানো যায়।

    যেমন—

    • Button Click করার পর Disable করে দেওয়া।
    • Loading Spinner দেখানো।
    • Request Complete না হওয়া পর্যন্ত দ্বিতীয় Click Block করা।

    এতে অনেক Duplicate Request শুরুতেই বন্ধ হয়ে যায়।

    কিন্তু…

    শুধু Frontend-এর উপর নির্ভর করা যাবে না।

    কারণ—

    • User Page Refresh করতে পারে।
    • Network Retry হতে পারে।
    • Mobile App একই Request আবার পাঠাতে পারে।
    • কোনো Bot Request পাঠাতে পারে।

    তাই আসল নিরাপত্তা Backend-এ থাকতে হবে।


    দ্বিতীয় প্রতিরক্ষা: Idempotency Key

    প্রতিটি Payment Request-এর সাথে একটি Unique Id পাঠানো হয়।

    যেমন—

    Idempotency-Key: 7f4c9d...
    

    যদি একই Key আবার আসে,

    Server নতুন Payment Process করবে না।

    আগের Result-ই Return করবে।


    তৃতীয় প্রতিরক্ষা: Database Constraint

    Database-এ এমনভাবে Design করুন,

    যাতে একই Transaction Reference দুইবার Insert-ই করা না যায়।

    যেমন—

    • PaymentReference
    • OrderId
    • TransactionId

    এসবের উপর Unique Constraint থাকতে পারে।

    Database অনেক সময় শেষ নিরাপত্তা দেয়।


    চতুর্থ প্রতিরক্ষা: Transaction

    Payment-এর গুরুত্বপূর্ণ Operation-গুলো Transaction-এর মধ্যে করুন।

    যাতে মাঝপথে কোনো সমস্যা হলে Partial Data Save না হয়।


    পঞ্চম প্রতিরক্ষা: Distributed Lock (যেখানে প্রয়োজন)

    যদি Multiple Server একই Payment Process করতে পারে,

    তাহলে Redis Distributed Lock-এর মতো সমাধান ব্যবহার করা যেতে পারে।

    এতে একই সময়ে একটি Payment শুধুমাত্র একটি Server Process করবে।


    সবচেয়ে গুরুত্বপূর্ণ শিক্ষা

    একজন User একই Request একাধিকবার পাঠাতে পারেন।

    Network একই Request Retry করতে পারে।

    Gateway একই Callback আবার পাঠাতে পারে।

    এসবই স্বাভাবিক।

    অস্বাভাবিক হলো—

    System সেই Request-গুলোকে নতুন Payment হিসেবে ধরে আবার টাকা কেটে ফেলছে।

    একটি ভালো Payment System-এর লক্ষ্য হওয়া উচিত—

    “One business operation = One successful payment.”

    যতবার Request আসুক না কেন।