Проблема использования многопоточности с простым приложением WPF - PullRequest
3 голосов
/ 19 августа 2010

Я совершенно новичок в WPF, я создал простое приложение WPF, которое отображает всю структуру диска (папки, файлы) в TreeView, так как этот процесс занимает некоторое время, я пытался использовать поток для запуска метода GetFolderTree ()и чтобы пользовательский интерфейс не реагировал, однако я сталкиваюсь с некоторыми проблемами. Я создал класс с именем FolderBrowser, где у меня есть весь код, собирающий структуру дисков, и внутри этого класса я создаю новый экземпляр TreeViewItem, который в конце содержит структуру дисков.используется в качестве возвращаемого значения для заполнения TreeView. Это код:

using System.IO;
using System.Windows.Controls;

namespace WpfApplication  
{
  public class FolderBrowser  
  {  
    private TreeViewItem folderTree;
    private string rootFolder;

    public FolderBrowser(string path)
    {
        rootFolder = path;
        folderTree = new TreeViewItem();
    }

    private void GetFolders(DirectoryInfo di, TreeViewItem tvi)
    {
        foreach (DirectoryInfo dir in di.GetDirectories())
        {
            TreeViewItem tviDir  = new TreeViewItem() { Header = dir.Name };         

            try
            {
                if (dir.GetDirectories().Length > 0)
                    GetFolders(dir, tviDir);

                tvi.Items.Add(tviDir);
                GetFiles(dir, tviDir);
            }
            //catch code here
        }

        if (rootFolder == di.FullName)
        {
            folderTree.Header = di.Name;
            GetFiles(di, folderTree);
        }
    }

    private void GetFiles(DirectoryInfo di, TreeViewItem tvi)
    {
        foreach (FileInfo file in di.GetFiles())
        {
            tvi.Items.Add(file.Name);
        }
    }

    public TreeViewItem GetFolderTree()
    {
        DirectoryInfo di = new DirectoryInfo(rootFolder);
        if (di.Exists)
        {                
            GetFolders(di, folderTree);                                
        }

        return folderTree;
    }
  }
}

Как я могу создать новые экземпляры элемента управления в этом новом потоке?

Заранее спасибо

Ответы [ 4 ]

2 голосов
/ 19 августа 2010

Если решение от Merkyn Morgan-Graham не работает (см. Мой комментарий, я не уверен), я предлагаю создать независимую объектную структуру, которая содержит ваши объекты каталогов.

Выполните эту работу с BackgroundWorker. Если он закончен, используйте эту структуру для непосредственного построения узлов TreeViewItem (потому что это не так медленно, если у вас их несколько сотен) или используйте его как ViewModel (лучше).

BackgroundWorker bgWorker = new BackgroundWorker();
bgWorker.DoWork += (s, e) => {
    // Create here your hierarchy
    // return it via e.Result                
};
bgWorker.RunWorkerCompleted += (s, e) => {
    // Create here your TreeViewItems with the hierarchy from  e.Result                
};
bgWorker.RunWorkerAsync();
1 голос
/ 19 августа 2010

Вы не можете взаимодействовать с пользовательским интерфейсом ни в одном потоке, кроме потока пользовательского интерфейса, но вы можете использовать объект диспетчера пользовательского интерфейса для выполнения обратного вызова внутри потока пользовательского интерфейса:

System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() => { /* your UI code here */ }));

Более "чистый"способ получения диспетчера заключается в передаче его из объекта пользовательского интерфейса в поток / класс, который порождает поток, при его создании.

Редактировать:

Iрекомендую решение HCL по моему.Однако в комментариях вы спросили, как заставить это работать без дублирования этого большого неприятного блока кода:

В вашем конструкторе возьмите ссылку на объект Dispatcher и сохраните его в своем классе.

Затем создайте метод, подобный этому:

private void RunOnUIThread(Action action)
{
    this.dispatcher.Invoke(action);
}

И назовите его так:

RunOnUIThread(() => { /* UI code */ });

Вы можете обернуть большие блоки кода следующим образом:

RunOnUIThread(() =>
{
  Console.WriteLine("One statement");
  Console.WriteLine("Another statement");
});

Если вы попытаетесь перенести слишком много этого кода обратно в пользовательский интерфейс, он будет ничем не отличается от того, если вы выполнили весь код в потоке пользовательского интерфейса, и все равно будет зависать в пользовательском интерфейсе.

Однако предложение HCL о заполнении пользовательской древовидной структуры вместо того, чтобы этот код знал что-либо об элементах управления пользовательского интерфейса, гораздо лучше:)

0 голосов
/ 19 августа 2010

Вот ответ с использованием MVVM (ну, по крайней мере, часть view / view-model) и фоновых рабочих потоков.При этом используется фоновый рабочий для заполнения модели представления (рекурсивно) и шаблон иерархических данных для привязки представления к модели представления.

Обратите внимание, что у нас все еще остается та же проблема с многопоточностью, поскольку рабочий поток не может изменитьсяObservableCollection.Итак, мы используем обработчик события RunWorkerCompleted (который выполняется в потоке пользовательского интерфейса) для заполнения коллекции.

MainWindow.xaml:

<Window
    x:Class="WpfApplication.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication">
    <StackPanel>
        <TextBlock Text="Contents:" />
        <TreeView ItemsSource="{Binding BaseDirectory.Contents}">
            <TreeView.Resources>
                <HierarchicalDataTemplate
                      DataType="{x:Type local:FileSystemEntry}"
                      ItemsSource="{Binding Contents}">
                    <TextBlock Text="{Binding Name}" />
                </HierarchicalDataTemplate>
            </TreeView.Resources>
        </TreeView>
    </StackPanel>
</Window>

MainWindowViewModel.cs: ​​

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;

namespace WpfApplication
{
    public class MainWindowViewModel
    {
        public MainWindowViewModel()
        {
            this.BaseDirectory = new FileSystemEntry("C:\\");
            this.BaseDirectory.Populate();
        }

        public FileSystemEntry BaseDirectory { get; private set; }
    }

    public class FileSystemEntry
    {
        public FileSystemEntry(string path)
            : this(new DirectoryInfo(path))
        {
        }

        private FileSystemEntry(DirectoryInfo di)
            : this()
        {
            this.Name = di.Name;
            this.directoryInfo = di;
        }

        private FileSystemEntry(FileInfo fi)
            : this()
        {
            this.Name = fi.Name;
            this.directoryInfo = null;
        }

        private FileSystemEntry()
        {
            this.contents = new ObservableCollection<FileSystemEntry>();
            this.Contents = new ReadOnlyObservableCollection<FileSystemEntry>(this.contents);
        }

        public string Name { get; private set; }

        public ReadOnlyObservableCollection<FileSystemEntry> Contents { get; private set; }

        public void Populate()
        {
            var bw = new BackgroundWorker();

            bw.DoWork += (s, e) =>
            {
                var result = new List<FileSystemEntry>();

                if (directoryInfo != null && directoryInfo.Exists)
                {
                    try
                    {
                        foreach (FileInfo file in directoryInfo.GetFiles())
                            result.Add(new FileSystemEntry(file));

                        foreach (DirectoryInfo subDirectory in
                            directoryInfo.GetDirectories())
                        {
                            result.Add(new FileSystemEntry(subDirectory));
                        }
                    }
                    catch (UnauthorizedAccessException)
                    {
                        // Skip
                    }
                }

                System.Threading.Thread.Sleep(2000); // Todo: Just for demo purposes

                e.Result = result;
            };

            bw.RunWorkerCompleted += (s, e) =>
            {
                var newContents = (IEnumerable<FileSystemEntry>)e.Result;

                contents.Clear();
                foreach (FileSystemEntry item in newContents)
                    contents.Add(item);

                foreach (FileSystemEntry subItem in newContents)
                    subItem.Populate();
            };

            bw.RunWorkerAsync();
        }

        private ObservableCollection<FileSystemEntry> contents;
        private DirectoryInfo directoryInfo;
    }
}
0 голосов
/ 19 августа 2010

Я бы посоветовал вам искать иерархические шаблоны вместо того, чтобы вручную строить дерево. Вы можете построить всю структуру в фоновом потоке, а затем связать полученную структуру данных с вашим деревом.

...