BackgroundWorker 在执行时给出线程错误

BackgroundWorker gives threading error when executing

在 WPF 应用程序中,我有一个 SQL 查询需要一些时间来执行,我想实现一个带有进度条的 BackgroundWorker,以便用户可以看到它何时完成。

这是 XAML 部分:

<Grid Grid.Row="2">
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <ProgressBar x:Name="BgProgBar" Margin="5"/>
    <TextBlock x:Name="Perc_TB" HorizontalAlignment="Center"
               VerticalAlignment="Center"
               Text="{Binding Path=Value,ElementName=BgProgBar}"/>
</Grid>

下面是我的 BackgroundWorker 代码:

  public partial class Supp_Stocks : Page
  {
    private string _user = Settings.Default.User;
    private DataTable dt;
    SqlConnection conn;
    SqlCommand comm;
    SqlConnectionStringBuilder connStringBuilder;
    BackgroundWorker Bg = new BackgroundWorker();
    public Supp_Stocks()
    {
        InitializeComponent();
        ConnectToDB();
        Bg.DoWork += Bg_DoWork;
        Bg.ProgressChanged += Bg_ProgressChanged;
        Bg.WorkerReportsProgress = true;

    }

    #region BackGroundWorker
    private void Bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        BgProgBar.Value = e.ProgressPercentage;
    }

    private void Bg_DoWork(object sender, DoWorkEventArgs e)
    {
        for(int i = 0; i < 100; i++)
        {
            try
            {
                comm = new SqlCommand("select STOCK_ENT.ENT_ID,ENT_DATE_ENT,ENT_NUMPAL,CLI_NOM,ART_LIBELLE1,ENT_PICKING,SUM(det_pnet)" +
                " as POIDS_NET from STOCK_DET, STOCK_ENT, CLIENTS, FICHES_ARTICLES, MVTS_SEQUENCE where SEQ_STATUT <> 'V' and" +
                " STOCK_ENT.ENT_ID = STOCK_DET.ENT_ID and STOCK_ENT.ENT_PROP = CLI_CODE and STOCK_ENT.ART_CODE = FICHES_ARTICLES.ART_CODE" +
                " and STOCK_ENT.ENT_ID = MVTS_SEQUENCE.ENT_ID group by STOCK_ENT.ENT_ID, ENT_DATE_ENT, ENT_NUMPAL, CLI_NOM, ART_LIBELLE1, ENT_PICKING order by ART_LIBELLE1", conn);
                SqlDataAdapter dap = new SqlDataAdapter(comm);
                dt = new DataTable();
                dap.Fill(dt);
                Stocks_DT.ItemsSource = dt.DefaultView;
                Bg.ReportProgress(i);
            }
            catch (Exception ex)
            {

                var stList = ex.StackTrace.ToString().Split('\');
                Messages.ErrorMessages($"ERREUR : \n{ex.Message}\n\nException at : \n{stList[stList.Count() - 1]}");
            }

        }
    }
    private void GenerateStk_Btn_Click(object sender, RoutedEventArgs e)
    {
        Bg.RunWorkerAsync();
    }
    #endregion

    private void ConnectToDB()
    {
        connStringBuilder = new SqlConnectionStringBuilder
        {
            DataSource = @"VM-VISUALSTORE\SQLEXPRESS,1433",
            InitialCatalog = "GSUITE",
            Encrypt = true,
            TrustServerCertificate = true,
            ConnectTimeout = 30,
            AsynchronousProcessing = true,
            MultipleActiveResultSets = true,
            IntegratedSecurity = true,
        };
        conn = new SqlConnection(connStringBuilder.ToString());
        comm = conn.CreateCommand();
    }
}

但是在执行代码时 returns 出现错误:

抱歉,它是法语的,但它说“该线程无法访问该对象,因为另一个线程拥有它。

你没有说代码中的哪一行抛出错误,但我很确定是这一行,对吧?

Stocks_DT.ItemsSource = dt.DefaultView;

Stocks_DT是你window上的控件,对吧?

这就是你问题的根源。如错误所述,您只能从 UI 线程(主线程)访问 UI 元素。由于您是 运行 后台线程上的上述代码,因此抛出此异常。

相反,您需要将查询结果传回主线程,然后将其分配给Stocks_DT.ItemsSource。我建议使用 DoWorkEventArgs.Result 属性。我建议使用 DataTable 作为结果,这样代码就是 e.Result = dt。您可以从 BackgroundWorker.RunWorkerCompleted 事件访问 Result


以上将解决异常,但我觉得我应该在您的代码中检查其他一些事情,纯粹是为了帮助您。

首先,您似乎在没有任何更改的情况下循环执行完全相同的查询 100 次。这似乎是浪费时间,因为您只会得到相同的结果 100 次。如果是这种情况,您可能根本不需要进度条。

其次,通常认为最好的做法是在需要时打开一个新的 SqlConnection 并在完成后关闭它,而不是在 program/window 的生命周期内保持一个连接打开。当您进入多线程时尤其如此。您可以在 this question 中找到更多信息。创建这些连接时,它们应该包含在 using 语句中。

第三,C#支持多行字符串,如this question所示。使用此方法将通过摆脱所有 "..." + "..."

使 SQL 查询更具可读性