You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为数据操作类实现最佳异步方法?

实现异步数据加载与更新(不阻塞UI + 进度显示)

首先,咱们要抓住几个核心原则:用原生异步IO方法而非线程池包装同步方法、通过IProgress<T>安全更新UI进度、正确处理异步异常。下面一步步修改你的代码:

1. 改造SourceElement的异步方法

SqlDataAdapter本身提供了FillAsync和UpdateAsync这些原生异步方法,这是真正的非阻塞IO操作,比用Task.Run包裹同步Fill/Update高效得多。我们可以直接利用这些方法,同时加入进度报告支持:

public sealed class SourceElement : IDisposable
{
    private readonly SqlDataAdapter _adapter;

    public SourceElement(SourceManager parentManager, string tableName)
    {
        ParentManager = parentManager;
        TableName = tableName;
        _adapter = new SqlDataAdapter($"SELECT * FROM [{TableName}];", ParentManager.ConnectionString);
        _adapter.FillSchema(ParentManager.MainDataSet, SchemaType.Mapped, TableName);
    }

    public void Dispose() => _adapter?.Dispose();

    public string TableName { get; }
    private SourceManager ParentManager { get; }

    public void Load() => _adapter.Fill(ParentManager.MainDataSet, TableName);

    // 异步加载,支持进度报告
    public async Task LoadAsync(IProgress<int> progress = null)
    {
        // 使用原生FillAsync,真正异步IO
        await _adapter.FillAsync(ParentManager.MainDataSet, TableName);
        // 加载完成后报告进度(这里每个元素算1个进度单位)
        progress?.Report(1);
    }

    public void Update() => _adapter.Update(ParentManager.MainDataSet.Tables[TableName]);

    // 异步更新,返回受影响行数,支持进度报告
    public async Task<int> UpdateAsync(IProgress<int> progress = null)
    {
        int affectedRows = await _adapter.UpdateAsync(ParentManager.MainDataSet.Tables[TableName]);
        // 报告受影响行数作为进度细节
        progress?.Report(affectedRows);
        return affectedRows;
    }
}

2. 改造SourceManager的异步方法

这里要处理多个SourceElement的异步操作,同时计算整体进度。你可以选择串行执行(更安全,避免DataSet并发问题)或者并行执行(效率更高,适合独立表操作):

串行版本(推荐,无并发风险)

public sealed class SourceManager : IDisposable
{
    // 原构造函数、Dispose、属性保持不变...

    public void LoadElements() => Elements.ForEach(element => element.Load());

    public async Task LoadElementsAsync(IProgress<int> progress = null)
    {
        int totalElements = Elements.Count;
        int completedCount = 0;

        foreach (var element in Elements)
        {
            await element.LoadAsync();
            completedCount++;
            // 计算百分比进度并报告
            int percent = (completedCount * 100) / totalElements;
            progress?.Report(percent);
        }
    }

    public void UpdateAll() => Elements.ForEach(element => element.Update());

    public async Task UpdateAllAsync(IProgress<int> progress = null)
    {
        int totalElements = Elements.Count;
        int completedCount = 0;

        foreach (var element in Elements)
        {
            await element.UpdateAsync();
            completedCount++;
            int percent = (completedCount * 100) / totalElements;
            progress?.Report(percent);
        }
    }
}

并行版本(适合独立表,效率更高)

如果你的每个SourceElement操作的是DataSet中不同的表,那么并行执行是安全的,能加快整体速度:

public async Task LoadElementsAsync(IProgress<int> progress = null)
{
    int totalElements = Elements.Count;
    int completedCount = 0;

    // 用WhenAll并行执行所有加载任务
    var tasks = Elements.Select(async element =>
    {
        await element.LoadAsync();
        // 用Interlocked保证并发下计数正确
        int currentCompleted = Interlocked.Increment(ref completedCount);
        int percent = (currentCompleted * 100) / totalElements;
        progress?.Report(percent);
    });

    await Task.WhenAll(tasks);
}

3. UI层调用(不阻塞+进度显示)

在WinForms中,Progress<T>类是神器——它会自动把进度回调调度到创建它的线程(也就是UI线程),所以你不需要手动调用Invoke来更新控件。同时要注意:UI事件处理方法用async void,其他异步方法返回Task,并且一定要捕获异常:

public partial class Form1 : Form
{
    private SourceManager sourceManager;

    public Form1()
    {
        InitializeComponent();
        sourceManager = new SourceManager("Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;");
    }

    protected override async void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        // 初始化进度控件(假设你有progressBar1和lblProgress两个控件)
        progressBar1.Maximum = 100;
        progressBar1.Value = 0;
        lblProgress.Text = "准备加载...";

        // 创建Progress实例,回调会自动在UI线程执行
        var loadProgress = new Progress<int>(percent =>
        {
            progressBar1.Value = percent;
            lblProgress.Text = $"加载中:{percent}%";
        });

        try
        {
            await sourceManager.LoadElementsAsync(loadProgress);
            lblProgress.Text = "加载完成!";
        }
        catch (Exception ex)
        {
            MessageBox.Show($"加载失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            lblProgress.Text = "加载失败";
        }
    }

    public async void SaveAll()
    {
        var saveProgress = new Progress<int>(percent =>
        {
            progressBar1.Value = percent;
            lblProgress.Text = $"保存中:{percent}%";
        });

        try
        {
            await sourceManager.UpdateAllAsync(saveProgress);
            lblProgress.Text = "保存完成!";
        }
        catch (Exception ex)
        {
            MessageBox.Show($"保存失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            lblProgress.Text = "保存失败";
        }
    }

    public async void SaveData(string tableName)
    {
        var element = sourceManager.Elements.Find(e => e.TableName.Equals(tableName));
        if (element == null)
        {
            lblProgress.Text = $"未找到表:{tableName}";
            return;
        }

        // 这里进度报告受影响行数
        var saveProgress = new Progress<int>(affectedRows =>
        {
            lblProgress.Text = $"保存{tableName}:{affectedRows}行已更新";
        });

        try
        {
            await element.UpdateAsync(saveProgress);
            lblProgress.Text = $"{tableName}保存完成!";
        }
        catch (Exception ex)
        {
            MessageBox.Show($"保存{tableName}失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            lblProgress.Text = $"{tableName}保存失败";
        }
    }
}

关键注意事项

  • 不要用Task.Run包装同步方法:FillAsync/UpdateAsync是原生异步IO,不会占用线程池线程,比Task.Run(() => element.Load())高效得多。
  • IProgress<T>的正确使用:必须在UI线程创建Progress<T>实例,这样回调才会自动在UI线程执行,避免跨线程操作控件的异常。
  • 异常处理:所有异步操作都要放在try/catch中,否则未捕获的异常会导致程序崩溃。
  • DataSet线程安全:DataSet本身不是线程安全的,但如果每个SourceElement操作的是不同的DataTable,并行执行是安全的;如果操作同一个表,必须串行。

内容的提问来源于stack exchange,提问作者Shehab

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:22:27