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 查询更具可读性
在 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所示。使用此方法将通过摆脱所有 "..." + "..."