首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> C# 入門

C# 非同步資料庫讀取與 UI 綁定:正確跨執行緒調度模式

     編輯:C# 入門

問題場景

WinForms/WPF 中,SqlCommand.BeginExecuteReader 回呼在**背景執行緒**執行,直接操作 UI 控制項 (dataGridView1.DataSource = dt) 會導致:
1. InvalidOperationException (跨執行緒存取,.NET 2.0+ 預設檢查)。
2. 若關閉 CheckForIllegalCrossThreadCalls = false,UI 凍結、重繪異常、進程無法正常退出。

錯誤示範


// ❌ 錯誤:回呼直接綁定 UI
private void delDataBin(IAsyncResult ar) {
    var com = (SqlCommand)ar.AsyncState;
    var dr = com.EndExecuteReader(ar);
    var dt = new DataTable(); dt.Load(dr);
    this.dataGridView1.DataSource = dt; // 拋異常或 UI 異常
}

正確模式:Invoke / BeginInvoke 調度回 UI 執行緒

WinForms


public partial class MainForm : Form {
    private delegate void UpdateGridDelegate(DataTable dt);

    private void btnLoad_Click(object sender, EventArgs e) {
        var connStr = "UID=sa;Password=123;Initial Catalog=AD;Data Source=192.168.1.1;Asynchronous Processing=true";
        using (var con = new SqlConnection(connStr)) {
            con.Open();
            using (var cmd = new SqlCommand("SELECT TOP 100 * FROM tb_user", con)) {
                cmd.BeginExecuteReader(ar => {
                    try {
                        var c = (SqlCommand)ar.AsyncState;
                        using var dr = c.EndExecuteReader(ar);
                        var dt = new DataTable(); dt.Load(dr);
                        // 關鍵:檢查是否需跨執行緒調度
                        if (this.InvokeRequired) {
                            this.BeginInvoke(new UpdateGridDelegate(BindGrid), dt);
                        } else {
                            BindGrid(dt);
                        }
                    } catch (Exception ex) {
                        this.BeginInvoke(() => MessageBox.Show($"Error: {ex.Message}"));
                    }
                }, cmd);
            }
        }
    }

    private void BindGrid(DataTable dt) {
        dataGridView1.DataSource = dt;
    }
}

WPF (Dispatcher)


// WPF 回呼中
Application.Current.Dispatcher.BeginInvoke(new Action(() => {
    dataGrid.ItemsSource = dt.DefaultView;
}), DispatcherPriority.DataBind);

現代化改寫:async/await + Task (推薦)

SqlCommand.ExecuteReaderAsync (ADO.NET 4.5+) 回傳 Task<SqlDataReader>,配合 await 自動捕獲同步上下文 (UI 執行緒),**無需手動 Invoke**。


private async void btnLoad_Click(object sender, EventArgs e) {
    btnLoad.Enabled = false;
    try {
        var connStr = "UID=sa;Password=123;Initial Catalog=AD;Data Source=192.168.1.1;Asynchronous Processing=true";
        using (var con = new SqlConnection(connStr)) {
            await con.OpenAsync();
            using (var cmd = new SqlCommand("SELECT TOP 100 * FROM tb_user", con)) {
                using (var dr = await cmd.ExecuteReaderAsync()) {
                    var dt = new DataTable();
                    dt.Load(dr);
                    dataGridView1.DataSource = dt; // 自動在 UI 執行緒執行
                }
            }
        }
    } catch (Exception ex) {
        MessageBox.Show($"Error: {ex.Message}");
    } finally {
        btnLoad.Enabled = true;
    }
}

進階:取消與超時


private CancellationTokenSource _cts;

private async void btnLoad_Click(object sender, EventArgs e) {
    _cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); // 30 秒超時
    try {
        // ... await con.OpenAsync(_cts.Token);
        // ... await cmd.ExecuteReaderAsync(_cts.Token);
    } catch (OperationCanceledException) {
        MessageBox.Show("查詢超時或已取消");
    } catch (Exception ex) {
        MessageBox.Show($"Error: {ex.Message}");
    }
}

private void btnCancel_Click(object sender, EventArgs e) => _cts?.Cancel();

關鍵原理總結

模式執行緒模型UI 調度適用版本
BeginExecuteReader + 回呼ThreadPool 執行緒手動 Invoke/BeginInvoke.NET 2.0+
ExecuteReaderAsync + awaitThreadPool,但 await 後續回 UI 執行緒自動 (同步上下文).NET 4.5+
BackgroundWorkerThreadPoolRunWorkerCompleted 事件在 UI 執行緒.NET 2.0+ (舊專案)

避坑清單

  1. **絕不**設定 Control.CheckForIllegalCrossThreadCalls = false — 掩蓋 Bug、導致不可預測 UI 錯誤。
  2. SqlConnection 連接字串**必須**包含 Asynchronous Processing=true (或 Async=true) 才能使用非同步方法。
  3. DataTable.Load 會完整讀取 SqlDataReader,大量資料考慮分頁或 SqlDataAdapter.Fill (同步) 配合 Task.Run。
  4. 非同步事件處理器 async void 僅限事件訂閱;其他場合用 async Task 以便錯誤傳播與等待。

結語

從 APM (Begin/End) 到 TAP (async/await),核心難點始終是**背景執行緒取得資料 → 安全調度回 UI 執行緒綁定**。掌握 InvokeRequired/BeginInvoke (WinForms) 或 Dispatcher (WPF) 的手動調度,並優先遷移至 async/await 享受自動同步上下文捕獲,是構建流暢、穩定資料驅動桌面應用的必修課。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved