none
WPF. Не удается организовать новый поток RRS feed

  • Вопрос

  • Добрый день. У меня есть класс для работы с файлами. В какой-то момент я заполняю коллекцию данными файлов из указанной директории. 
    public class PhotoCollection : ObservableCollection<Photo>
    {
            public PhotoCollection() { }
    
            public PhotoCollection(string path) : this(new DirectoryInfo(path)) { }
    
            public PhotoCollection(DirectoryInfo directory)
            {
                _directory = directory;
                Update();
            }
    
            public string Path
            {
                set
                {
                    _directory = new DirectoryInfo(value);
                    Update();
                }
                get { return _directory.FullName; }
            }
    
            private void Update()
            {
                this.Clear();
                try
                {
                    int cnt = 0;
                    foreach (FileInfo f in _directory.GetFiles("*.jpg"))
                    {
                        Add(new Photo(f.FullName, cnt));
                        cnt++;
                    }
    
                }
                catch (DirectoryNotFoundException)
                {
                    MessageBox.Show("Указанная директория не найдена!", "", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
    
            DirectoryInfo _directory;
    }
    Хотелось бы вынести заполнение в новый поток. Но при попытке сделать так внутри класса:
    private void LoadFiles()
    {
         int cnt = 0;
         foreach (FileInfo f in _directory.GetFiles("*.jpg"))
         {
               Add(new Photo(f.FullName, cnt));
               cnt++;
               Thread.Sleep(0);
         }
    }
    
    Thread myThread = new Thread(LoadFiles); //Создаем новый объект потока (Thread)
    myThread.Start(); //запускаем поток
    myThread.Join();
    Вылетает исключение: "Данный тип CollectionView не поддерживает изменения в своем SourceCollection из потока, отличного от потока Dispatcher." Почитал, что надо использовать Dispatcher.Invoke. Подскажите, пожалуйста, как правильно обернуть данный фрагмент кода Dispatcher.Invoke?
    25 ноября 2017 г. 13:49

Все ответы

  • Увы, в WPF (и всех других популярных UI) все операции с любыми UI элементами и привязками должны производится исключительно на UI потоке.

    Что до "обернуть фрагмент", то это сделает всю затею бесполезной так как код по факту код будет выполнен на UI потоке и никаких преимуществ не будет, только лишняя сложность и шансы полного зависания.

    На деле ваш текущий вариант совершенно бесполезен так как он блокирует UI поток вызовом Thread.Join() на все время выполнение процедуры. Это значит что приложение перестанет отвечать на все это время, точно так как оно перестанет отвечать на то же самое время если бы вы это делали на UI потоке.

    Выше я упомянул о шансах полного зависания... Увы, именно это и произойдет при "оборачивании". Вот как это произойдет:

    1. UI поток запустит рабочий поток, после чего UI поток будет немедленно заблокирован ожидая окончания работы рабочего потока.

    2. Рабочий поток начнет получать список файлов и в какой то момент вызовет Dispatcher.Invoke для выполнения операции на UI потоке.

    3. Dispatcher.Invoke будет заблокирован до тех пор пока UI поток не будет разблокирован для выполнения кода.

    4. Но UI поток заблокирован до тех пор пока рабочий поток не завершится. А он, в свою очередь, заблокирован пока UI поток не выполнит операцию.

    Получаем классический деадлок. Использование потока не только не изменила ситуацию со скоростью работы, но и сделал все много хуже...

    В общем, у вас пара вариантов (если уберете блокировку UI потока конечно):

    1. Уберите привязку коллекции к UI (на UI потоке), заполните список (на рабочем потоке), возобновите привязку (на UI потоке).

    2. Поместите данные во временный список (на рабочем потоке), поменяйте содержимое списка (на UI потоке).

    Более продвинутые средства включают в себя создание базового списка который автоматически использует Dispatcher.Invoke, и/или поддерживает временное отключение событий. Такой код несложно найти на просторах интернета.

    Что до исполнения кода на UI потоке, то тут все просто:

    Dispatcher.Invoke(() => /* Тут код исполняется на UI потоке. */);


    This posting is provided "AS IS" with no warranties, and confers no rights.

    25 ноября 2017 г. 17:59
    Модератор
  • Наверное, на новых версиях Net нужно просто сделать LoadFiles асинхронным Task'ом и вызывать его с помощью await, а не прописывать Thread.Join. Тогда от многопоточности действительно будет смысл.  
    25 ноября 2017 г. 20:24
  • Наверное, на новых версиях Net нужно просто сделать LoadFiles асинхронным Task'ом и вызывать его с помощью await, а не прописывать Thread.Join. Тогда от многопоточности действительно будет смысл.  

    Увы, все не так просто с async/awit. Скажем, UWP приложения (где использование async/await faktiyeski неизбежно) не показывают каких либо преимуществ в скорости перед Win32 аналогами, скорее наоборот. Так же из-за неявных (и не очевидных) методов синхронизации нередки зависания и падения вызванные их применением. Я уже не говорю о том что async должен быть добавлен практически ко всему коду.


    This posting is provided "AS IS" with no warranties, and confers no rights.

    25 ноября 2017 г. 22:30
    Модератор