0Pricing
C# Academy · บทเรียน

ConfigureAwait และบริบทการซิงโครไนซ์

ทำความเข้าใจ SynchronizationContext ใช้ ConfigureAwait(false) ในโค้ดไลบรารี และหลีกเลี่ยงภาวะหยุดชะงักในกระแสงานอะซิงโครนัส

ConfigureAwait และบริบทการซิงโครไนซ์ เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

SynchronizationContext คืออะไร

SynchronizationContext ควบคุมวิธีจัดกำหนดการโค้ดต่อเนื่องหลัง await ในแอป UI (WPF, WinForms) จะช่วยให้การอัปเดต UI เกิดขึ้นบนเธรด UI ใน ASP.NET Core จะไม่มีบริบทการซิงโครไนซ์ (ถูกนำออกไปแล้ว) การทำความเข้าใจเรื่องนี้เป็นกุญแจสำคัญในการเขียนโค้ดอะซิงโครนัสที่ถูกต้อง

การทำงานของ ConfigureAwait

.ConfigureAwait(false) บอกตัวรอว่า ไม่ต้องจับบริบท SynchronizationContext ปัจจุบันไว้เพื่อกลับมาทำงานต่อ การทำงานต่อจะทำงานบนเธรดใดก็ได้ในกลุ่มเธรดแทน

// Default: captures context (resumes on original context)
var data = await FetchDataAsync();
UpdateUI(data); // runs on UI thread — correct for UI apps

// ConfigureAwait(false): doesn't capture context
var data2 = await FetchDataAsync().ConfigureAwait(false);
// Runs on any thread — DON'T access UI thread-only objects here

รูปแบบเดดล็อกแบบคลาสสิก

การบล็อก Task ด้วย .Result หรือ .Wait() ขณะที่ถือบริบทการทำงานให้เกิดเดดล็อกแบบคลาสสิก เพราะการทำงานต่อรอให้บริบทที่กำลังบล็อกเพื่อรอการทำงานต่อนั้นกลับมาทำงาน

// DEADLOCK in WPF/WinForms or ASP.NET classic:
string result = GetDataAsync().Result; // blocks UI thread
// GetDataAsync() tries to resume on UI thread after await
// UI thread is blocked by .Result
// DEADLOCK!

// Fix 1: await properly (never block)
string result = await GetDataAsync();

// Fix 2: use ConfigureAwait(false) in the library
public async Task<string> GetDataAsync()
    => await client.GetStringAsync(url).ConfigureAwait(false);

ConfigureAwait(false) ในไลบรารี

โค้ดไลบรารีที่ไม่ขึ้นกับบริบทควรใช้ ConfigureAwait(false) กับการรอทุกครั้งเสมอ วิธีนี้จะป้องกันเดดล็อกเมื่อผู้เรียกใช้ไลบรารีจากสภาพแวดล้อมที่ขึ้นกับบริบท

// Library code — always ConfigureAwait(false)
public async Task<User?> GetUserAsync(int id)
{
    var json = await client
        .GetStringAsync($"/users/{id}")
        .ConfigureAwait(false);

    var user = JsonSerializer.Deserialize<User>(json);
    return user;
}

// Application code (has context) — leave ConfigureAwait default
public async Task LoadUserProfileAsync(int id)
{
    var user = await userService.GetUserAsync(id); // context captured
    ProfileLabel.Text = user?.Name; // safe to update UI
}

ASP.NET Core: ไม่มี SynchronizationContext

ASP.NET Core ตั้งใจไม่ใช้ SynchronizationContext การทำงานต่อจะทำงานบนกลุ่มเธรด ConfigureAwait(false) จึงไม่ทำอะไรในทางเทคนิค แต่ยังเป็นแนวปฏิบัติที่ดีสำหรับโค้ดที่ใช้ร่วมกับเฟรมเวิร์กอื่น

// In ASP.NET Core, both are equivalent:
var data = await service.GetAsync();                      // fine
var data2 = await service.GetAsync().ConfigureAwait(false); // also fine

// Context-free means: no deadlock risk from .Result in ASP.NET Core
// BUT: mixing with libraries that assume a context is still risky
// BEST PRACTICE: still use ConfigureAwait(false) in library code

การจัดตารางเวลาบนกลุ่มเธรดเทียบกับบริบท

เมื่อไม่มีบริบท การทำงานต่อจะถูกจัดตารางเวลาโดย TaskScheduler ซึ่งโดยทั่วไปคือ ThreadPoolTaskScheduler แต่เมื่อมีบริบทของส่วนติดต่อผู้ใช้ การทำงานต่อจะถูกส่งไปยังลูปข้อความของส่วนติดต่อผู้ใช้

// Check current synchronization context:
Console.WriteLine(SynchronizationContext.Current?.GetType().Name
    ?? "No context (thread pool)");

// In a WPF event handler: "DispatcherSynchronizationContext"
// In ASP.NET Core: null (no context)
// In a unit test with xUnit: "AsyncTestSyncContext"

// After ConfigureAwait(false):
await Task.Delay(1).ConfigureAwait(false);
Console.WriteLine(SynchronizationContext.Current?.GetType().Name
    ?? "No context"); // null — context was abandoned

ConfigureAwait ในลูป

เมื่อรอภายในลูป การทำงานต่อของแต่ละรอบจะทำงานในบริบทที่จับไว้ เว้นแต่จะใช้ ConfigureAwait(false) ในโค้ดไลบรารี ควรใช้วิธีนี้อย่างสม่ำเสมอกับการรอทุกครั้งในลูป

// Library loop: ConfigureAwait(false) on every await
public async Task ProcessBatchAsync(IEnumerable<int> ids)
{
    foreach (var id in ids)
    {
        var item = await FetchItemAsync(id).ConfigureAwait(false);
        await SaveItemAsync(item).ConfigureAwait(false);
    }
}

ValueTask และ ConfigureAwait

ValueTask รองรับ ConfigureAwait(false) เช่นกัน ควรใช้อย่างสม่ำเสมอตามกฎเดียวกับ Task

public async ValueTask<string> GetFromCacheAsync(string key)
{
    // ValueTask with ConfigureAwait(false)
    var raw = await _store.GetAsync(key).ConfigureAwait(false);
    return raw ?? "";
}

ใช้ Task.Run เพื่อหลีกเลี่ยงบริบท

ใช้ Task.Run เพื่อย้ายงานที่ใช้ CPU ไปทำงานบนกลุ่มเธรด และทำให้มั่นใจว่างานนั้นจะไม่ทำงานบนเธรดที่ถือบริบท เช่น เธรดของส่วนติดต่อผู้ใช้

// WRONG in UI app: blocks UI thread
void ButtonClick(object? sender, EventArgs e)
{
    var result = HeavyComputation(); // blocks UI
    Label.Text = result;
}

// CORRECT: offload to thread pool
async void ButtonClick(object? sender, EventArgs e)
{
    var result = await Task.Run(() => HeavyComputation());
    // Continuation captured UI context: safe to update UI
    Label.Text = result;
}

กรณีใช้งานจริง: ไลบรารีไคลเอนต์ HTTP ที่ใช้ ConfigureAwait

ตัวห่อหุ้มไคลเอนต์ HTTP ในระบบจริงใช้ ConfigureAwait(false) ตลอดทั้งโค้ด เพื่อให้ปลอดภัยเมื่อถูกเรียกใช้จากบริบทใดก็ตาม

public class ApiClient
{
    private readonly HttpClient _http;
    private readonly JsonSerializerOptions _opts = new(JsonSerializerDefaults.Web);

    public ApiClient(HttpClient http) => _http = http;

    public async Task<T> GetAsync<T>(string path, CancellationToken ct = default)
    {
        using var response = await _http
            .GetAsync(path, ct)
            .ConfigureAwait(false);

        response.EnsureSuccessStatusCode();

        return await response.Content
            .ReadFromJsonAsync<T>(_opts, ct)
            .ConfigureAwait(false)
            ?? throw new InvalidOperationException("Null response");
    }
}

ตรวจสอบความเข้าใจ

เหตุใดการเรียกใช้ .Result กับ Task ในแอป WPF หรือ WinForms จึงมักทำให้เกิดเดดล็อก

สรุป: ConfigureAwait และ SynchronizationContext

ประเด็นสำคัญ:

  • SynchronizationContext: จัดตารางเวลาการทำงานต่อให้กลับไปยังเธรดเฉพาะ เช่น เธรดของส่วนติดต่อผู้ใช้
  • ConfigureAwait(false): ยกเลิกการจับบริบท และทำงานต่อบนกลุ่มเธรด
  • โค้ดไลบรารี: ใช้ ConfigureAwait(false) เสมอเพื่อป้องกันเดดล็อก
  • โค้ดแอปพลิเคชันที่มีส่วนติดต่อผู้ใช้: ใช้ await แบบค่าเริ่มต้นเพื่อกลับไปยังเธรดของส่วนติดต่อผู้ใช้
  • ASP.NET Core: ไม่มี SynchronizationContext แต่ยังเป็นแนวปฏิบัติที่ดีสำหรับไลบรารีที่ใช้ร่วมกัน
  • อย่าบล็อก Task ด้วย .Result/.Wait() ในแอปที่ขึ้นกับบริบท

คำถามที่พบบ่อย

บทเรียน “ConfigureAwait และบริบทการซิงโครไนซ์” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ConfigureAwait และบริบทการซิงโครไนซ์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ConfigureAwait และบริบทการซิงโครไนซ์”

ทำความเข้าใจ SynchronizationContext ใช้ ConfigureAwait(false) ในโค้ดไลบรารี และหลีกเลี่ยงภาวะหยุดชะงักในกระแสงานอะซิงโครนัส คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “ConfigureAwait และบริบทการซิงโครไนซ์” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม

ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. IAsyncEnumerable และ await foreach
  2. System.Threading.Channels
  3. ValueTask และการหลีกเลี่ยงการจัดสรรหน่วยความจำ
  4. ConfigureAwait และบริบทการซิงโครไนซ์
← กลับไปที่ C# Academy