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

异步从MSSQL获取产品并更新全局静态列表的实现难题

异步导入商品数据并实时更新DataGrid的问题排查与优化

需求背景

我正在从MSSQL数据库导入约20000条商品数据,把这个操作放在独立任务里执行。获取数据后要异步填充一个全局静态列表,这个列表后续会作为DataGrid的数据源,所以需要DataGrid能实时展示已导入的商品。

初始代码实现及卡顿问题

触发操作的按钮事件逻辑:

// 应用中需执行两项任务:从数据库导入大量商品,填充全局可用列表
// 该列表将在其他窗口打开时使用,目的是提前加载好列表,以便在内存中搜索商品
// 触发所有操作的事件:导入商品并填充全局列表:
private void btnImportArticles_Click(object sender, RoutedEventArgs e) { 
    if (MessageBox.Show("Sure?", "Data sync", MessageBoxButton.YesNo) != MessageBoxResult.Yes) 
        return; 
    Task.Factory.StartNew(() => ImportDataFromServer()) // 从数据库导入商品和分组
        .ContinueWith(task => { 
            // 在MSSQL导入完成后启动新任务更新全局可用列表,调用PrepareArticles()方法异步存储大量商品
            task.ContinueWith(task2 => { 
                PrepareArticles(); // 但应用严重卡顿
            }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); 
        }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); 
}

核心导入方法:

private void ImportDataFromServer() { 
    using (SqlConnection connection = GetSqlConnection()) { 
        connection.Open(); 
        ImportGroups(); 
        ImportArticles(); 
    } 
}

从数据库获取数据的方法:

private void ImportArticles() { 
    List<Article> newArticles = new List<Article>(); 
    using (SqlConnection connection = GetSqlConnection()) { 
        connection.Open(); 
        using (SqlCommand command = new SqlCommand()) { 
            command.CommandText = "SELECT COUNT(*) FROM [dbo].[Products] T1 INNER JOIN [dbo].[ProductGroup] T2 ON T1.Code = T2.Code"; 
            command.Connection = connection; 
        } 
        using (SqlCommand command = new SqlCommand()) { 
            command.CommandText = "SELECT T1.[Code], [Title], [Description],[Price] FROM [dbo].[Products] T1 INNER JOIN [dbo].[ProductGroup] T2 ON T1.Code = T2.Code"; 
            command.Connection = connection; 
            using (SqlDataReader reader = command.ExecuteReader()) { 
                while (reader.Read()) { 
                    // 简化代码 
                } 
            } 
        } 
    } 
}

private void ImportGroups() { 
    using (SqlConnection connection = GetSqlConnection()) { 
        connection.Open(); 
        using (SqlCommand command = new SqlCommand()) { 
            command.CommandText = "SELECT [GroupId] FROM [dbo].[Groups]"; 
            command.Connection = connection; 
            using (SqlDataReader reader = command.ExecuteReader()) { 
                if (reader.Read()) { 
                    // 简化代码 
                } 
            } 
        } 
    } 
    using (SqlConnection connection = GetSqlConnection()) { 
        connection.Open(); 
        using (SqlCommand command = new SqlCommand()) { 
            command.CommandText = "SELECT [GroupId], [Title] FROM [dbo].[GroupsArticles]"; 
            command.Connection = connection; 
            SqlDataReader reader = command.ExecuteReader(); 
            while (reader.Read()) { 
                // 简化代码 
            } 
        } 
    } 
}

填充全局列表的方法:

private void PrepareArticles() { 
    Task.Factory.StartNew(() => Globals.GetArticlesReady()) 
        .ContinueWith(task3 => { 
        }, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); 
}

public static void GetArticlesReady() { 
    // 导入完成后从数据库获取所有商品 
    Articles = new List<Article>(); 
    Articles = ArticlesController.GetAll(); 
}

遇到的问题:调用PrepareArticles()时应用出现严重卡顿。

经Camilo帮助后的代码修改

private async void btnImportArticles_Click(object sender, RoutedEventArgs e) { 
    if (MessageBox.Show("Sure?", "Data sync", MessageBoxButton.YesNo) != MessageBoxResult.Yes) 
        return; 
    // 调用ImportDataFromServer从数据库获取商品 
    await ImportDataFromServer(); 
    // 将MSSQL中的商品导入本地数据库后,获取数据填充C#全局列表 
    await PrepareArticles(); 
}

private async Task ImportDataFromServer() { 
    using (SqlConnection connection = GetSqlConnection()) { 
        await connection.OpenAsync(); 
        // 父方法是Task,子方法是否无需声明为Task? 
        ImportGroups(); 
        ImportArticles(); 
    } 
}

private async void ImportGroups() { 
    using (SqlConnection connection = GetSqlConnection()) { 
        await connection.OpenAsync(); 
        using (SqlCommand command = new SqlCommand()) { 
            command.CommandText = "SELECT [GroupId] FROM [dbo].[Groups]"; 
            command.Connection = connection; 
            using (SqlDataReader reader = await command.ExecuteReaderAsync()) { 
                if (await reader.ReadAsync()) { 
                    // 简化代码 
                } 
            } 
        } 
    } 
    using (SqlConnection connection = GetSqlConnection()) { 
        connection.OpenAsync(); 
        using (SqlCommand command = new SqlCommand()) { 
            command.CommandText = "SELECT [GroupId], [Title] FROM [dbo].[GroupsArticles]"; 
            command.Connection = connection; 
            SqlDataReader reader = await command.ExecuteReaderAsync(); 
            while (await reader.ReadAsync()) { 
                // 简化代码 
            } 
        } 
    } 
}

private async void ImportArticles() { 
    List<Article> newArticles = new List<Article>(); 
    using (SqlConnection connection = GetSqlConnection()) { 
        await connection.OpenAsync(); 
        using (SqlCommand command = new SqlCommand()) { 
            command.CommandText = "SELECT COUNT(*) FROM [dbo].[Products] T1 INNER JOIN [dbo].[ProductGroup] T2 ON T1.Code = T2.Code"; 
            command.Connection = connection; 
        } 
        using (SqlCommand command = new SqlCommand()) { 
            command.CommandText = "SELECT T1.[Code], [Title], [Description],[Price] FROM [dbo].[Products] T1 INNER JOIN [dbo].[ProductGroup] T2 ON T1.Code = T2.Code"; 
            command.Connection = connection; 
            using (SqlDataReader reader = await command.ExecuteReaderAsync()) { 
                while (await reader.ReadAsync()) { 
                    // 简化代码 
                } 
            } 
        } 
    } 
}

// 应声明为Task还是仅async void?
private async Task PrepareArticles() { 
    // 父方法是Task,此方法是否可保留为async void? 
    Globals.GetArticlesReady(); 
}

public static async void GetArticlesReady { 
    // 从之前导入的数据库中获取商品 
    Articles = new List<Article>(); 
    Articles = ArticlesController.GetAll(); 
}

全局列表使用场景补充:

public SearchForArticlesForm() { 
    InitializeComponent(); 
    // 使用全局列表是为了打开该窗口时无需等待15秒加载所有商品 
    // 因此提前在独立任务中加载 
    databaseArticles = new ObservableCollection<Article>(Globals.Articles); 
    dtgArticles.ItemsSource = databaseArticles; 
}

仍存在的问题:应用依旧出现冻结现象。

经mm8帮助后的修改及新错误

private async void btnImportArticles_Click(object sender, RoutedEventArgs e) { 
    try { 
        if (MessageBox.Show("Sure?", "Data sync", MessageBoxButton.YesNo) != MessageBoxResult.Yes) 
            return; 
        await Task.Run(() => { 
            // 执行数据库操作 
            ImportDataFromServer(); 
        }); 
        // 此处本想填充全局列表,但未执行就报错 
    } 
    catch (Exception ex) { 
        // 错误信息:调用线程无法访问该对象,因为另一个线程拥有它 
        MessageBox.Show(ex.Message); 
    } 
}

补充细节:导入过程中会更新进度条,ImportArticles方法内调用了UpdateProgressBarOnImport:

// 注:ImportArticles是ImportDataFromServer的子方法
private void ImportArticles() { 
    List<Article> newArticles = new List<Article>(); 
    using (SqlConnection connection = GetSqlConnection()) { 
        connection.Open(); 
        using (SqlCommand command = new SqlCommand()) { 
            command.CommandText = "SELECT COUNT(*) FROM [dbo].[Products] T1 INNER JOIN [dbo].[ProductGroup] T2 ON T1.Code = T2.Code"; 
            command.Connection = connection; 
        } 
        using (SqlCommand command = new SqlCommand()) { 
            command.CommandText = "SELECT T1.[Code], [Title], [Description],[Price] FROM [dbo].[Products] T1 INNER JOIN [dbo].[ProductGroup] T2 ON T1.Code = T2.Code"; 
            command.Connection = connection; 
            using (SqlDataReader reader = command.ExecuteReader()) { 
                while (reader.Read()) { 
                    // 调用UpdateProgressBarOnImport方法 
                    UpdateProgressBarOnImport(..); 
                } 
            } 
        } 
    } 
}

进度条更新方法:

public void UpdateProgressBarOnImport (double percentage) { 
    Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { 
        progressBar.SetValue(ProgressBar.ValueProperty, percentage); 
    }, null); 
}

新错误:调用线程无法访问该对象,因为另一个线程拥有它。


内容的提问来源于stack exchange,提问作者Roxy'Pro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:56:22