0

我有一个ObservableCollection自定义类,它包含一个字符串和一个 int:

public class SearchFile
{
    public string path { set; get; }
    public int occurrences { set; get; }
}

我想在dataGrid. 该集合具有在更新时通知的方法,到目前为止,只需将其链接到DataGrid.ItemsSource(正确的?)。这是网格 XAML(dataGrid1.ItemsSource = files;在 C# 代码隐藏中):

        <DataGrid AutoGenerateColumns="False" Height="260" Name="dataGrid1" VerticalAlignment="Stretch" IsReadOnly="True" ItemsSource="{Binding}" >
            <DataGrid.Columns>
                <DataGridTextColumn Header="path" Binding="{Binding path}" />
                <DataGridTextColumn Header="#" Binding="{Binding occurrences}" />
            </DataGrid.Columns>
        </DataGrid>

现在事情变得更复杂了。我首先要显示path默认值occurrence为零的 s。然后,我想遍历每一个SearchFile并用计算值更新它occurrence。这是辅助函数:

    public static void AddOccurrences(this ObservableCollection<SearchFile> collection, string path, int occurrences)
    {
        for(int i = 0; i < collection.Count; i++)
        {
            if(collection[i].path == path)
            {
                collection[i].occurrences = occurrences;
                break;
            }
        }
    }

这是占位符工作函数:

    public static bool searchFile(string path, out int occurences)
    {
        Thread.Sleep(1000);
        occurences = 1;
        return true; //for other things; ignore here     
    }

我正在使用 aBackgroundWorker作为后台线程。就是这样:

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {           
        List<string> allFiles = new List<string>();
        //allFiles = some basic directory searching

        this.Dispatcher.Invoke(new Action(delegate
        {
            searchProgressBar.Maximum = allFiles.Count;
            files.Clear(); // remove the previous file list to build new one from scratch
        }));

        /* Create a new list of files with the default occurrences value. */
        foreach(var file in allFiles)
        {
            SearchFile sf = new SearchFile() { path=file, occurrences=0 };
            this.Dispatcher.Invoke(new Action(delegate
            {
                files.Add(sf);
            }));
        }

        /* Add the occurrences. */
        foreach(var file in allFiles)
        {
            ++progress; // advance the progress bar
            this.Dispatcher.Invoke(new Action(delegate
            {
                searchProgressBar.Value = progress;
            }));

            int occurences;
            bool result = FileSearcher.searchFile(file, out occurences);

            files.AddOccurrences(file, occurences);
        }
    }

现在当我运行它时,有两个问题。首先,更新进度条的值会引发The calling thread cannot access this object because a different thread owns it.异常。为什么?它在调度程序中,所以它应该可以正常工作。其次,foreach循环bool result =...在行上中断。我将其注释掉并尝试设置int occurences = 1,然后循环继续进行,但发生了一些奇怪的事情:每当我调用该方法时,它要么全为零,全为一,要么处于中间状态,onez 在看似随机的数字之后开始零)。

为什么?

4

1 回答 1

0

Dispatcher 并不像听起来那么简单。使用调度程序意味着代码将在创建包含对象的同一线程上执行,但这不一定是 UI 线程。确保在屏幕上显示的实际 UI 元素中定义了 worker_DoWork 方法(这只是消除后台线程创建的元素的简单方法)。

真的,看看你的代码,我不确定你为什么要使用后台工作人员。这看起来实际上会更慢,因为不断地分派给 UI 线程。相反,我认为您更好的选择是将长时间运行的部分放入任务中,并在单个回调中更新 UI。例如,在您的代码中,对于 UI 线程而言实际上太慢的唯一事情可能是 FileSearcher 调用。这很容易放入一个返回找到的结果数量的后台任务。

至于 FileSearcher 上的问题,您的方法定义不匹配。您发布的方法只需要一个路径和 out int,但是当您调用它时,您会传递 4 个参数。如果没有看到您实际上调用的超载,很难猜测出了什么问题。

编辑:让我再备份一点。您的根本问题是 WPF 不支持绑定到从后台线程修改的集合。这就是那里所有调度和其他复杂代码的原因。我最初的建议(长期工作的一项任务)是解决问题的一种方法。但是使用ObservableList 类可能会做得更好。这使您可以从后台线程更新集合,并自动通知 UI 而无需使用调度程序。这可能会使大多数复杂的线程问题消失。我也建议阅读那里的前三篇文章以供参考,那里有很多很好的信息。

于 2012-11-21T13:56:20.667 回答