在BeginInvoke内部调用时,长操作阻塞

时间:2017-09-12 07:03:02

标签: c# wpf parallel-processing task-parallel-library

我的程序在MDB中搜索每个Table& Row中的给定字符串。 当我开始搜索(Search_Button_Click)时,进度条没有显示,并且ui正在阻塞(无法移动窗口)。不能搞错了。

ListViewDataObservableCollection<ListViewData>

     private async void Search_Button_Click(object sender, RoutedEventArgs e)
    {
        LoadBar.Visibility = Visibility.Visible;
        await StartSearch();
        LoadBar.Visibility = Visibility.Hidden;
    }

    private async Task StartSearch()
    {
        await Task.Run(() =>
        {
            SearchMDB();
        });
    }

    private void SearchMDB()
    {
        this.Dispatcher.BeginInvoke(new Action(() =>
        {
            ListViewData.Clear();

            foreach (KeyValuePair<string, DataTable> _KVP in MDBContent)
            {
                for (int RowIndex = 0; RowIndex < _KVP.Value.Rows.Count; RowIndex++)
                {
                    DataRow _DR = _KVP.Value.Rows[RowIndex];
                    for (int i = 0; i < _DR.ItemArray.Length; i++)
                    {
                        if (_DR[i].ToString().Contains(Search_TextBox.Text))
                        {
                            ListViewItemClass _LC = new ListViewItemClass();
                            _LC.Page = _KVP.Key;
                            _LC.Column = _DR.Table.Columns[i].ToString();
                            _LC.Row = (RowIndex + 1).ToString();
                            _LC.ItemValue = _DR[i].ToString();
                            ListViewData.Add(_LC);
                        }
                    }
                }
            }
        }));
    }

2 个答案:

答案 0 :(得分:3)

您的代码是同步的。 BeginInvoke用于封送回UI线程的调用。使用Task.Run来呼叫BeginInvoke不会改变任何内容。

我假设您从名称SearchMDB开始尝试在MDB数据库上执行LIKE搜索。最好的选择是让 Access 执行此操作。 Access有索引。你的代码没有。它被迫扫描所有数据。更好的是,找到一个可以处理MDB文件的全文搜索库。将所有内容加载到内存中实际上可以使事情更慢

如果您希望此代码按原样运行,只需使用Task.Run并将过滤器字符串作为参数传递给SearchMDB,例如StartSearch(Search_TextBox.Text)

private async void Search_Button_Click(object sender, RoutedEventArgs e)
{
    LoadBar.Visibility = Visibility.Visible;
    await Task.Run(StartSearch(Search_TextBox.Text));
    LoadBar.Visibility = Visibility.Hidden;
}

private void SearchMDB()
{
    ListViewData.Clear();

    foreach (KeyValuePair<string, DataTable> _KVP in MDBContent)
    {
        .....
    }
}

更好的是,避免全局ListViewData容器。使用全局状态时编写正确的多线程代码非常困难。错误处理也更难 - 如果SearchMDB失败,您打算做什么?

假设ListViewDataList<ListViewItemClass>,您应该写:

private async void Search_Button_Click(object sender, RoutedEventArgs e)
{
    LoadBar.Visibility = Visibility.Visible;
    ListViewData=await Task.Run(StartSearch(Search_TextBox.Text));
    LoadBar.Visibility = Visibility.Hidden;
}

private List<ListViewItemClass> SearchMDB()
{
    var newData=new List<ListViewItemClass>();

    foreach (KeyValuePair<string, DataTable> _KVP in MDBContent)
    {
      for ()
      {
        .....
        newData.Add(_LC);
      }
    }
    return newData();
}

这样可以避免并发错误SearchMDB抛出时不会破坏您的UI。

<强>更新

整个方法可以重写为单个LINQ查询:

var items = from KeyValuePair<string, DataTable> pair in MDBContent
            from DataRow row in pair.Value.Rows
            from DataColumn column in pair.Value.Columns
            let field=row[column].ToString()
            where field.Contains(searchText)
            select new ListViewItemClass
            {
                Page = pair.Key,
                Column = column.Caption,
//                Row = (RowIndex + 1).ToString(),
                ItemValue = field
            };

不仅更清楚发生了什么,只需调用`.AsParallel()就可以轻松将其转换为PLINQ,例如:

var items = from KeyValuePair<string, DataTable> pair in MDBContent.AsParallel()
            from DataRow row in pair.Value.Rows
            from DataColumn column in pair.Value.Columns
            let field=row[column].ToString()
            where field.Contains(searchText)
            select new ListViewItemClass
            {
                Page = pair.Key,
                Column = column.Caption,
//                Row = (RowIndex + 1).ToString(),
                ItemValue = field
            };
return items.ToList();

请注意,没有Row字段。表行具有行索引。它们在结果中的位置由ORDER BY子句控制。没有它,数据库可以不按顺序返回结果。

如果使用传递inded的Select()重载以及要投影的项目,则可以引入行索引:

var items = from pair in MDBContent.AsParallel()
            let indexedRows =pair.Value.Rows.OfType<DataRow>().Select((row,idx)=>new {Row=row,Idx=idx})                
            from indexedRow in indexedRows
            from DataColumn column in pair.Value.Columns
            let field=indexedRow.Row[column].ToString()
            where field.Contains(searchText)
            select new ListViewItemClass
            {
                Page = pair.Key,
                Column = column.Caption,
                Row = (indexedRow.Idx +1).ToString(),
                ItemValue = field
            };

更新2

另一个问题中的评论显示ListViewData是一个ObservableCollection。这不会改变任何事情。数据仍应在侧面处理。 ObservableCollection旨在观察单个项目的更改。

在这种情况下,整个集合会发生变化。处理此问题的最简单方法是替换集合并引发其相应属性更改的通知,从而强制UI重新加载数据。这就是WPF数据绑定的工作原理,通过绑定到属性而不是字段。它也便宜得多 - 清理集合并逐个添加项目会引发很多的通知。

点击事件处理程序应更改为:

private async void Search_Button_Click(object sender, RoutedEventArgs e)
{
    LoadBar.Visibility = Visibility.Visible;
    var data=await Task.Run(StartSearch(Search_TextBox.Text));

    ListViewData=new ObservableCollection(data);

    //Raise a change notification if `ListViewData` isn't a property
    //or doesn't raise the event itself
    //RaisePropertyChanged("ThatPropertyName);

    LoadBar.Visibility = Visibility.Hidden;
}

答案 1 :(得分:2)

我可以看到两个问题:

A)。 SearchMDB()方法已经异步执行。您可以删除this.Dispatcher.BeginInvoke()行,因为您再次回到UI线程上运行。

B)。但!您正在更新该代理中的UI!更好的做法是异步线程从DB获取所需的所有数据(如果需要,创建一个小的DTO类),然后在UI线程上填充/刷新ListView。

private async Task StartSearch()
{
    var data = await SearchAndFetchMDBDataAsync();
    RefreshListView(data);
}

private Task<List<object>> SearchAndFetchMDBDataAsync()
{
    return Task.Run(() =>
    {
        List<MdbDto> data = new List<MdbDto>();

        foreach (KeyValuePair<string, DataTable> _KVP in MDBContent)
            // ...

        return data;
    });
}