BackgroundWorker读取数据库并更新GUI

问题描述:

当我查询数据库并更新 BackgroundWorker DoWork 中的DataGrid时,我试图使不确定的ProgressBar动画平稳运行.我了解我无法在 DoWork 中更新UIThread,因此我使用Dispatcher.BeginInvoke访问DataGrid,但是我仍然收到由另一个线程拥有的异常.

I am trying to keep my indeterminate ProgressBar animation running smoothly as I query database and updating DataGrid in DoWork in BackgroundWorker. I understand that I cannot update UIThread in DoWork, so I use Dispatcher.BeginInvoke to access DataGrid, yet I still receive the owned-by-another-thread exception.

问题1

为什么会这样? DoWork应该处于开启状态 UIThread和Dispatcher拥有的BackgroundWorker线程 允许我的DoWork访问我的GUI.

Why is this happening? DoWork should be on BackgroundWorker's thread owned by UIThread, and Dispatcher should allow my DoWork to access my GUI.

进一步查看BackgroundWorker,建议UI操作应在 ProgressChanged() 中处理.但是,如果我将所有数据库/GUI操作移至 ProgressChanged() 并将Thread.Sleep(5000)放在 DoWork() 上,以模拟需要处理的内容,从而为留出足够的时间> ProgressChanged() 运行,尽管ProgressBar继续其不确定的平滑动画,但GUI尚未更新.

Looking further into BackgroundWorker suggest UI operation should be handled in ProgressChanged(). But if I move all database/GUI operation to ProgressChanged() and put a Thread.Sleep(5000) on DoWork() to emulate something to work on to lend enough time for ProgressChanged() to run, the GUI is not being updated, although the ProgressBar continue its indeterminate smooth animation.

问题2

我在BackgroundWorker线程上调用了Thread.Sleep(5000). ProgressChanged()应该花5秒钟查询数据库,然后 更新GUI.为什么不呢?

I called a Thread.Sleep(5000) on BackgroundWorker thread. ProgressChanged() should spend 5 seconds querying database and updating GUI. Why not?

XAML:

<DataGrid x:Name="myDataGrid" ScrollViewer.CanContentScroll="True" 
          EnableRowVirtualization="True" EnableColumnVirtualization="True"
          VirtualizingPanel.IsContainerVirtualizable="True" 
          VirtualizingPanel.IsVirtualizing="True"
          VirtualizingPanel.IsVirtualizingWhenGrouping="True"
          Height="500" MaxHeight="500" />

<ProgressBar x:Name="myProgressBar" Height="20" Width="400"
             IsIndeterminate="True" Visibility="Visible" />

<Button x:Name="mySearch" Click="btnSearch_Click">Search</Button>

C#

private BackgroundWorker bgw = new BackgroundWorker();

private void btnSearch_Click(object sendoer, RoutedEventArgs e)
{
    bgw.WorkerReportsProgress = true;
    bgw.ProgressChanged += ProgressChanged;
    bgw.DoWork += DoWork;
    bgw.RunWorkerCompleted += BGW_RunWorkerCompleted;

    bgw.RunWorkerAsync();
}

private void DoWork(object sender, DoWorkEventArgs e)
{
    // Thread.Sleep(5000);
    using (SqlConnection conn = new SqlConnection(connStr))
    {
        conn.Open();
        using (SqlCommand cmd = new SqlCommand("SELECT * FROM " + MyTableName, conn))
        {
            using (SqlDataReader rdr = cmd.ExecuteReader())
            {
                while (rdr.Read())
                {
                    Dispatcher.BeginInvoke(new Action(() =>
                    {
                        myDataGrid.Items.Add(new
                        {
                            Id = rdr.GetInt32(0),
                            Name = rdr.GetString(1).ToString(),
                        });
                    }));
                }
            }
        }
    }
}

private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
   // Paste code from DoWork and uncomment Thread.Sleep(5000)
}

private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}

在您的DoWork事件处理程序中创建一个项目集合,然后在RunWorkerCompleted中将DataGridItemsSource属性设置为该属性.事件处理程序完成背景线程上的数据提取后:

Create a collection of items in your DoWork event handler and then set the ItemsSource property of the DataGrid to this one in the RunWorkerCompleted event handler once you are done fetching the data on a background thread:

private void btnSearch_Click(object sendoer, RoutedEventArgs e)
{
    bgw.WorkerReportsProgress = true;
    bgw.ProgressChanged += ProgressChanged;
    bgw.DoWork += DoWork;
    bgw.RunWorkerCompleted += BGW_RunWorkerCompleted;

    myProgressBar.Visibility = Visibility.Visible;
    bgw.RunWorkerAsync();
}

private void DoWork(object sender, DoWorkEventArgs e)
{
    List<object> results = new List<object>();
    using (SqlConnection conn = new SqlConnection(connStr))
    {
        conn.Open();
        using (SqlCommand cmd = new SqlCommand("SELECT * FROM " + MyTableName, conn))
        {
            using (SqlDataReader rdr = cmd.ExecuteReader())
            {
                while (rdr.Read())
                {
                    results.Add(new
                    {
                        Id = rdr.GetInt32(0),
                        Name = rdr.GetString(1).ToString(),
                    });
                }
            }
        }
    }

    e.Result = results;
}

private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    myDataGrid.ItemsSource = e.Result as List<object>;
    myProgressBar.Visibility = Visibility.Collapsed;
}

您需要调用BackgroundWorkerReportProgress方法来报告任何进度,但是由于始终不了解实际进度,因此从数据库检索操作中报告进度是毫无意义的.最好使用不确定的ProgressBar并在开始操作时显示它,并在操作完成时将其隐藏,如上面的示例代码所示.

You need to call the ReportProgress method of the BackgroundWorker for any progress to be reported but it is pretty meaningless to report progress from a database retrieval operation since you have no clue about the actual progress anyway. You'd better just use an indeterminate ProgressBar and show it when you start the operation and hiding it when the operation has completed as demonstrated in the sample code above.

Thread.Sleep将阻止当前线程.如果您阻止UI线程,则您的UI(包括ProgressBar)将无法更新,因此您不想这样做.

And Thread.Sleep will block the current thread. If you block the UI thread, your UI, including your ProgressBar, cannot be updated so you don't want to do this.