Форум программистов, компьютерный форум, киберфорум
C# .NET
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.75/40: Рейтинг темы: голосов - 40, средняя оценка - 4.75
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923

Синхронизация асинхронного заполения списка и CancellationToken

06.10.2021, 14:07. Показов 8430. Ответов 95
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Открываю GUI страницу со списком картинок.
Проблема: из-за того, что картинки весят много -- какое-то время, допустим 2 секунды, страница просто пустая, а после резко заполняется всем списком.

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

Решение 1: решил сделать само заполнение асинхронным (просьба сначала досмотреть все 3 решения)
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
        protected async Task UpdateAll()
        {
            var devices = await devicesRepository.GetDevices(currentDevice == null ? Root : currentDevice.Id);
 
            await Task.Run(() =>
            {
                if (devices != null)
                {
                    var filterDevices = devices.FindAll(d => !d.IsArchived);
 
                    //remove old items
                    foreach (var oldItem in Devices)
                    {
                        if (filterDevices.Find(x => x.Id == oldItem.Id) == null)
                            Devices.Remove(oldItem);
                    }
 
                    //add or change new items
                    foreach (var newItem in filterDevices)
                    {
                        var elm = Devices.FirstOrDefault(x => x.Id == newItem.Id);
                        if (elm != null)
                        {
                            if (!newItem.Equals(elm))
                                elm = newItem;
                        }
                        else
                        {
                            Devices.Add(newItem);
                        }
                    }
                }
                else
                {
                    Devices.Clear();
                }
            });
 
            await UpdateContextMenu();
        }
Это решило мою проблему, но так я оставляю код потоконебезопасным.
Решение 2: добавил локировку
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
        protected async Task UpdateAll()
        {
            var devices = await devicesRepository.GetDevices(currentDevice == null ? Root : currentDevice.Id);
 
            await Task.Run(() =>
            {
                lock (((ICollection)Devices).SyncRoot)
                {
                    if (devices != null)
                    {
                        var filterDevices = devices.FindAll(d => !d.IsArchived);
 
                        //remove old items
                        foreach (var oldItem in Devices)
                        {
                                if (filterDevices.Find(x => x.Id == oldItem.Id) == null)
                                Devices.Remove(oldItem);
                        }
 
                        //add or change new items
                        foreach (var newItem in filterDevices)
                        {
                            var elm = Devices.FirstOrDefault(x => x.Id == newItem.Id);
                            if (elm != null)
                            {
                                if (!newItem.Equals(elm))
                                    elm = newItem;
                            }
                            else
                            {
                                Devices.Add(newItem);
                            }
                        }
                    }
                    else
                    {
                        Devices.Clear();
                    }
                }
            });
 
            await UpdateContextMenu();
        }
Сильно сомневаюсь что такой подход можно назвать "потокобезопасным"
Далее я подумал, что если я выйду из страницы и резко зайду обратно -- у меня перепутаются все элементы и, весьма вероятно, продублируются или ещё чего похуже.
Решение 3: я подумал, что во время закрытия страницы будет уместным сделать cancelTokenSource.Cancel();
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
        private static CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
        private CancellationToken token = cancelTokenSource.Token;
        protected async Task UpdateAll()
        {
            await UpdateAllTest();
        }
 
 
        protected async Task UpdateAllTest()
        {
            var devices = await devicesRepository.GetDevices(currentDevice == null ? Root : currentDevice.Id);
 
            await Task.Run(() =>
            {
                lock (((ICollection)Devices).SyncRoot)
                {
                    if (devices != null)
                    {
                        var filterDevices = devices.FindAll(d => !d.IsArchived);
 
                        //remove old items
                        foreach (var oldItem in Devices)
                        {
                            if (token.IsCancellationRequested)
                                return;
 
                                if (filterDevices.Find(x => x.Id == oldItem.Id) == null)
                                Devices.Remove(oldItem);
                        }
 
                        //add or change new items
                        foreach (var newItem in filterDevices)
                        {
                            if (token.IsCancellationRequested)
                                return;
 
                            var elm = Devices.FirstOrDefault(x => x.Id == newItem.Id);
                            if (elm != null)
                            {
                                if (!newItem.Equals(elm))
                                    elm = newItem;
                            }
                            else
                            {
                                Devices.Add(newItem);
                            }
                        }
                    }
                    else
                    {
                        Devices.Clear();
                    }
                }
                cancelTokenSource.Cancel();
            });
 
            await UpdateContextMenu();
        }
Когда закрываю страницу делаю:
C#
1
2
3
4
5
        public override void Deactivate()
        {
            base.Deactivate();
            cancelTokenSource.Cancel();
        }
Ужасный код, я понимаю. Ищу помощи тут.

Добавлено через 9 минут
Также хотел упомянуть следущий момент:
C#
1
2
3
4
await Task.Run(() =>
{
    lock (((ICollection)Devices).SyncRoot)
    //..
тут мне кажется я вообще полную чушь написал.
0
Лучшие ответы (1)
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
06.10.2021, 14:07
Ответы с готовыми решениями:

BlockingCollection<T>, Add(T item, CancellationToken cancellationToken);
Возник вопрос в механизме метода Add() BlockingCollection. В реализации BlockingCollection есть возможность добавить как элемент...

Зачем нужен CancellationToken?
у CancellationTokenSource есть тот-же свойство IsCancellationRequested, и метод Cancel вызывается у CancellationTokenSource. Зачем они...

Не происходит отмена задачи (CancellationToken)
wpf создаю private CancellationTokenSource cts = new CancellationTokenSource(); на команде кнопки вперед реализую: private...

95
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
06.10.2021, 14:23
limeniye, обычно разделяют загрузку коллекции Uri (или других источников) картинок и загрузку самих картинок.
Второе происходит только после обращения к ним в UI.

Вы уже вроде пытались использовать решение из Асинхронная подгрузка изображений
Что не получилось?

В данном, случае вы пытаетесь в Модели угадать действия UI и соответственно провести их обработку.
Тем самым у вас возникает паразитная зависимость Модели от UI.

Схема должна быть очень простая.
У Модели запросили данных - он их отдала.
Если получение коллекции данных долгий процесс, надо возвращать коллекцию идентификаторов (имён, ссылок, Uri, путей и т.д.).
А основные данные для каждого элемента возвращать отдельным методом по запросу для каждого элемента.
1
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
06.10.2021, 15:04  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
В данном, случае вы пытаетесь в Модели угадать действия UI и соответственно провести их обработку.
Тем самым у вас возникает паразитная зависимость Модели от UI.
Хотел бы сделать ремарку.

Начну с того, что в данном случае это не Модель, а ViewModel.

Я использую паттерн MVVM.
Стандартом MVVM, для меня, является следующая схема:


Но если зайти в гугл и написать "схема mvvm" -- то самым популярным будет абсолютно другая:


Лично моё мнение: мне второй способ не нравится, я считаю его полным нарушением самого принципа MVVM(субъективное мнение).
Но именно второй способ реализован во всём легаси проекте.

Касательно Асинхронная подгрузка изображений. Конкретно этот проект -- Xamarin, в нём нет того же image.Freeze()

Всё вышеперечисленное не имеет особого смысла, ибо загрузка изображений в проекте происходит из VM в Code-Behind.
При чём код-бихайнд, от части, выступает в качестве ViewModel, но наследуется от Grid.

Возможно я бы и попытался использовать вышепредложенный Вами способ, но в этом участке архитектуры я бы ничего не менял, ибо тот же Code-Behind ИЗОБРАЖЕНИЯ (под изображение выделили отдельный контрол) содержит 632 строки.

Надеюсь я достаточно расписал проблему, почему я делаю асинхронность добавления элементов в список аж на уровне VM.
Потому что всё что ниже -- какая-то каша, я ничего не понимаю, сложно читать, сложно расширять.

Зная то, как хорошо Вы разбираетесь в MVVM -- боюсь даже рассказывать подробности архитектуры -- Вам потом будут сниться кошмары.
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
06.10.2021, 15:12  [ТС]
Всё, что ниже этой VM, -- каша, я стараюсь на уровне этой VM хоть как-то всё оптимизировать (да, вот таким я вижу решение из этой ситуации).
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
06.10.2021, 15:37
Цитата Сообщение от limeniye Посмотреть сообщение
Но если зайти в гугл и написать "схема mvvm" -- то самым популярным будет абсолютно другая:
Эта схема не MVVM.
Вернее это MVVM, но в ней неверные обозначения блоков.
То что называют Моделью - это отражение БД.
А Модель (из паттерна приложения) здесь просто не обозначена и к ней относится репозиторий и всё что с ним связано.
Составлял кто-то имеющий опыт работы с БД в разных паттернах, и применивший (не совсем верно) свой опыт для составления этой якобы MVVM схемы.

Добавлено через 1 минуту
Если открыть доки MS (а MVVM разработан ими), то там видно что нет никаких репозиториев, а Моделью называется всё что "ниже" ViewModel.
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
06.10.2021, 16:13  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Составлял кто-то имеющий опыт работы с БД в разных паттернах, и применивший (не совсем верно) свой опыт для составления этой якобы MVVM схемы.
я изучал этот вопрос, ибо когда я попал на этот легаси проект -- я не понимал что происходит и углубился в интернет. Этот паттерн более распространён чем тот, который я показывал первым.

У меня на работе первый паттерн вообще первый раз видят: как разработчики WPF проектов, так и разработчики UWP.

________________________________________ ________________________________________ __________________
В данный момент я смирился с тем, что модель есть далеко не всегда и далеко не всё что модель -- настоящая модель.
А следовательно вернусь к теме.

Конкретно в ТС уже каша на моей стороне и я это объективно понимаю. Можете глянуть?

Напомню желаемый результат: чтобы можно было асинхронно добавлять элементы в список, но потокобезопасно, а также при переключении на прошлую страницу остановить асинхронный поток

Я это делал так
C#
1
2
if(token.IsCancellationRequested)
    return;
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
06.10.2021, 19:19
Цитата Сообщение от limeniye Посмотреть сообщение
Этот паттерн более распространён чем тот, который я показывал первым
Это схема для MVVM в которой отсутствует Бизнес Логика.
И всё что делает Модель - это обеспечивает работу с Репозиторием.
Кроме того, Репозиторий это тоже Модель.
И не никакого криминала в том, что VM будет работать напрямую с Репозиторием без каких-то промежуточных слоёв.

Скорее всего, те с кем вы общались, имеют дело только с такими проектами, в которых нужна реализация GUI для работы с БД.
Если появляется хоть малейшая Бизнес Логика, то (настоящая) Модель неминуемо возникнет.

Цитата Сообщение от limeniye Посмотреть сообщение
Я это делал так
Я пока не понял, что собственно вы хотите реализовать.

Давайте по пунктам:
1) Есть некая коллекция Devices, которая каким-то образом представлена в GUI:
- Что за тип элементов этой коллекции?
- Можете ли вы менять его реализацию?
- Как синхронизируется эта коллекция с привязками?
- Есть ли необходимость в изменении коллекции поэлементно или она всегда меняется целиком?

2) Метод UpdateAll:
- Чей это член: VM? Модели?
- Должен одновременно работать только один вызов метода?
- Если да, то что должно происходить при повторном вызове: исключение, ожидание, прерывание предыдущего?

3) Картинки:
- Где они находятся и в каком типе?
- Как и в каком типе передаёте их Представлению?
1
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
06.10.2021, 20:46  [ТС]
1)
Цитата Сообщение от Элд Хасп Посмотреть сообщение
- Что за тип элементов этой коллекции?
Вообще это
C#
1
ObservableCollection<DeviceViewModel> Devices { get; } = new ObservableCollection<DeviceViewModel>();
НО, ради эксперемента я делаю через AsyncObservableCollection (взята где-то у Вас).

Цитата Сообщение от Элд Хасп Посмотреть сообщение
- Можете ли вы менять его реализацию?
Если вопрос заключается в том -- библиотека это, или открытый проект -- это открытый проект. Но не совсем понял вопрос.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Как синхронизируется эта коллекция с привязками?
C#
1
2
3
4
5
6
7
            await Task.Run(() =>
            {
                lock (((ICollection)Devices).SyncRoot)
                {
                    // тут стараюсь синхронизировать
                }
            }
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Есть ли необходимость в изменении коллекции поэлементно или она всегда меняется целиком?
В 90% случаев данные будут приходить старые -- поэтому я просто проверяю есть ли какие-то измения и изменяю 1-2 элемента.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
                    if (devices != null)
                    {
                        var filterDevices = devices.FindAll(d => !d.IsArchived);
 
                        //remove old items
                        foreach (var oldItem in Devices)
                        {
                            if (token.IsCancellationRequested)
                                return;
 
                                if (filterDevices.Find(x => x.Id == oldItem.Id) == null)
                                Devices.Remove(oldItem);
                        }
 
                        //add or change new items
                        foreach (var newItem in filterDevices)
                        {
                            if (token.IsCancellationRequested)
                                return;
 
                            var elm = Devices.FirstOrDefault(x => x.Id == newItem.Id);
                            if (elm != null)
                            {
                                if (!newItem.Equals(elm))
                                    elm = newItem;
                            }
                            else
                            {
                                Devices.Add(newItem);
                            }
                        }
                    }
                    else
                    {
                        Devices.Clear();
                    }
2)
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Чей это член: VM? Модели?
UpdateAll -- часть VM.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Должен одновременно работать только один вызов метода?
тут я сильно сомневаюсь с ответом. Может быть такое, что метод запустится, я резко перейду на прошлую страницу и вернусь снова на эту. Получится так, что у меня запустится 2 метода UpdateAll. Тем не менее и сбрасывать, если уже один запустился -- по типу
C#
1
2
if(isBusy)
    return;
не вижу смысла. Тут должна быть их синхронизация: один метод за другим. Я же просто сделал токен отмены при выходе из страницы.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Если да, то что должно происходить при повторном вызове: исключение, ожидание, прерывание предыдущего?
Прервывание предыдущего.

3)
Цитата Сообщение от Элд Хасп Посмотреть сообщение
3) Картинки:
- Где они находятся и в каком типе?
- Как и в каком типе передаёте их Представлению?
У DeviceViewModel есть свойства
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        public Guid CategoryId
        {
            get => _categoryId;
            set
            {
                if (_categoryId == value) return;
                _categoryId = value;
                OnPropertyChanged(nameof(CategoryId));
            }
        }
 
        public Guid PhotoId
        {
            get => _photoId;
            set
            {
                _photoId = value;
                OnPropertyChanged(nameof(PhotoId));
            }
        }
Эти свойства попадают в код-бихайнд, он их смотрит и в зависимотри от их значений работает с другими моделями:
либо отправляет какой-то запрос, либо берёт локально.

Если отталкиваться от Вашего вопроса: какого он типа, то на этот вопрос сложно ответить, по коду он получается обобщённым.
Его пихают сюда:
XML
1
<ContentView x:Name="EntityImageView" .../>
Ваш третий пункт я бы лучше оставил, мне страшно в него лезть. Давайте на 1 и 2 остановимся лучше .
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
06.10.2021, 21:02
Цитата Сообщение от limeniye Посмотреть сообщение
я делаю через AsyncObservableCollection
Не стоит.
Эта тема была больше ради любопытства и познания.
Реализация не практична.

Добавлено через 2 минуты
Цитата Сообщение от limeniye Посмотреть сообщение
У DeviceViewModel есть свойства
Но это просто GUID.
А где сама картинка?

Добавлено через 1 минуту
Цитата Сообщение от limeniye Посмотреть сообщение
Ваш третий пункт я бы лучше оставил, мне страшно в него лезть.
Так если задержка у вас из-за загрузки картинок, то как вы собираетесь решить это проблему не разобравшись с их загрузкой?

Добавлено через 1 минуту
Цитата Сообщение от limeniye Посмотреть сообщение
Вообще это
Сделайте обычную ObservableCollection и синхронизацию привязок.
Этого будет достаточно.

Добавлено через 1 минуту
Цитата Сообщение от limeniye Посмотреть сообщение
В 90% случаев данные будут приходить старые -- поэтому я просто проверяю есть ли какие-то измения и изменяю 1-2 элемента.
Это занимает микросекунды.
А у вас подвешивается GUI на секунды.
Разница в три порядка.
Где конкретно возникает пробблема?
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
06.10.2021, 21:06  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Но это просто GUID.
А где сама картинка?
сама картинка может быть разных типов.
Один из примеров:
XML
1
<ContentView x:Name="EntityImageView"/>
C#
1
EntityImageView.Content = _entityGraphicOperation.CanvasView;
В данном случае CanvasView -- тип SKCanvasView, взят из какой-то библиотеки. Наследует View -> VisualElement и т.д.
Но может быть не SKCanvasView, а обычный Image. А если берётся фото откуда-то локально, то ещё какой-то тип.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
06.10.2021, 21:24
Цитата Сообщение от limeniye Посмотреть сообщение
Эти свойства попадают в код-бихайнд, он их смотрит и в зависимотри от их значений работает с другими моделями:
либо отправляет какой-то запрос, либо берёт локально.
DeviceViewModel - это тип предназначен для представления.
Он должен отдавать готовую картинку АСИНХРОННО, чтобы не подвешивать GUI.

Должно быть нечто подобное:

Свойство задающее ID картинки.
C#
1
2
3
4
5
6
7
8
9
10
11
       public Guid PhotoId
        {
            get => _photoId;
            set
            {
                if (Set (ref _photoId, value))
                {
                     ResetImageAsync(value);
                }
            }
        }
Свойство возвращающее картинку:
C#
1
2
3
4
5
        public ImageSource PhotoImage
        {
            get => _photoImage;
            private set => Set (ref _photoImage, value);
        }
Метод загружающий картинку:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private readonly object imageLocker = new object();
public ImageSource DefaultImage {get;}
private async void  ResetImageAsync(Guid imageGuid)
{
     PhotoImage = DefaultImage;
     ImageSource image = await someModel.GetImageAsync(imageGuid);
     lock(imageLocker)
     {
          if(PhotoId == image.Guid)
          {
               PhotoImage = image;
          }
     }
}
Если в методе ResetImageAsync возможны исключение, то их надо либо маршалировать в основной поток, либо обрабатывать внутри него.

Добавлено через 1 минуту
Цитата Сообщение от limeniye Посмотреть сообщение
сама картинка может быть разных типов.
Мне кажется вы путаете картинку (ImageSource) с элементом который её будет представлять.

Добавлено через 4 минуты
Цитата Сообщение от limeniye Посмотреть сообщение
В данном случае CanvasView -- тип SKCanvasView, взят из какой-то библиотеки. Наследует View -> VisualElement и т.д.
Но может быть не SKCanvasView, а обычный Image. А если берётся фото откуда-то локально, то ещё какой-то тип.
Вообще, какая-то каша....
Как это согласует с вашим из-за того, что картинки весят много -- какое-то время, допустим 2 секунды, страница просто пустая?

Добавлено через 59 секунд
limeniye, чё то у меня складывается впечатление, что вы сами не знаете в чём проблема и просто наугад бахаете то туда, то сюда.
2
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
06.10.2021, 21:51  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
limeniye, чё то у меня складывается впечатление, что вы сами не знаете в чём проблема и просто наугад бахаете то туда, то сюда.
Все страницы в проекте с списками сильно лагают. Почему? Так сразу не скажешь, в этом нужно очень глубоко разбираться не день, не два и не три. Прошлый разработчик в этом не разбирался.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Должно быть нечто подобное:
Весь нижепредставленный Вами код -- на стороне VM.

Я понимаю, но тут всё это на стороне код-бихайнд.

Чтобы сделать, что делаете Вы -- нужно поднять весь кодбихайнд в VM. Там 600 строк, я не хочу компаться в этом несколько дней.

Методом тыка, по какой-то неизвестной мне причине, асинхронизация следующего кода...
с
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
            if (devices != null)
            {
                var filterDevices = devices.FindAll(d => !d.IsArchived);
 
                //remove old items
                foreach (var oldItem in Devices)
                {
                    if (filterDevices.Find(x => x.Id == oldItem.Id) == null)
                        Devices.Remove(oldItem);
                }
 
                //add or change new items
                foreach (var newItem in filterDevices)
                {
                    var elm = Devices.FirstOrDefault(x => x.Id == newItem.Id);
                    if (elm != null)
                    {
                        if (!newItem.Equals(elm))
                            elm = newItem;
                    }
                    else
                    {
                        Devices.Add(newItem);
                    }
                }
            }
            else
            {
                Devices.Clear();
            }
на
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
            await Task.Run(() =>
            {
                if (devices != null)
                {
                    var filterDevices = devices.FindAll(d => !d.IsArchived);
 
                    //remove old items
                    foreach (var oldItem in Devices)
                    {
                        if (filterDevices.Find(x => x.Id == oldItem.Id) == null)
                            Devices.Remove(oldItem);
                    }
 
                    //add or change new items
                    foreach (var newItem in filterDevices)
                    {
                        var elm = Devices.FirstOrDefault(x => x.Id == newItem.Id);
                        if (elm != null)
                        {
                            if (!newItem.Equals(elm))
                                elm = newItem;
                        }
                        else
                        {
                            Devices.Add(newItem);
                        }
                    }
                }
                else
                {
                    Devices.Clear();
                }
            });
... загружает сами элементы последовательно не тормозя саму страницу.

Не их картинки, Вы правильно уточнили, а именно сами элементы уже с отрисованными картинками.

Почему так? -- мне не ясно, но я хочу пока закрепить этот вариант, потом уже думать над другим.

Как я хочу его закрепить? Я хочу сделать потокобезопасность этого варианта или схожего.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
06.10.2021, 23:24
Лучший ответ Сообщение было отмечено limeniye как решение

Решение

Цитата Сообщение от limeniye Посмотреть сообщение
Я хочу сделать потокобезопасность этого варианта или схожего.
У вас же есть вариант с локировкой.
Работает? Используйте его, если вы не хотите глубоко залазить.

Только вот с токеном отмены - по-моему, неправильно используете.
Его же надо в задачу передавать: Task.Run(Action, CancellationToken).
1
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
07.10.2021, 00:12  [ТС]
Элд Хасп, спасибо. Всё то, что ожидал услышать (с первого ответа до текущего).

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

Я сам вижу, что делаю не совсем так, да ещё и он у меня срабатывает 1 раз, а потом когда я открываю снова страницу — он остаётся Canceled ибо у меня его реализация кривая.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
07.10.2021, 02:16
limeniye, ну, так весь смыл его в том, что если экземпляр отменён, то остаётся всегда отменённым.
То есть вам надо определиться по алгоритму, когда надо завершить текущий токен - это повлечёт отмену всех задач.
Отменённый экземпляр активировать заново невозможно.
Когда вам нужно следующий раз его активировать, то нужно создавать новый экземпляр.

Добавлено через 1 минуту
Когда вы переходите на новую страницу, вы сначала отменяете текущий экземпляр.
Потом создаёте новый экземпляр.
Заменяете им текущий.
И после этого все запускаемые задачи будет использовать новый экземпляр.
1
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
07.10.2021, 02:35  [ТС]
Когда вы переходите на новую страницу, вы сначала отменяете текущий экземпляр.
Потом создаёте новый экземпляр.
Заменяете им текущий.
Элд Хасп, вот тут важное уточнение: это всё на выходе?
Ибо как это вижу я: при выходе из страницы я отменяю текущий экземпляр, а на входе — создаю новый.

Вход на страницу.
C#
1
2
3
4
5
6
7
8
9
        private static CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
        private CancellationToken token = cancelTokenSource.Token;
       
        public override void Activate()
        {
            base.Activate();            
            cancelTokenSource = new CancellationTokenSource();
            token = cancelTokenSource.Token;
        }

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
        protected async Task UpdateAll()
        {
            await UpdateAllWithCancellionChecker();
        }
 
 
        private async Task UpdateAllWithCancellionChecker()
        {
            var devices = await devicesRepository.GetDevices(currentDevice == null ? Root : currentDevice.Id);
 
            await Task.Run(() =>
            {
                lock (((ICollection)Devices).SyncRoot)
                {
                    if (devices != null)
                    {
                        var filterDevices = devices.FindAll(d => !d.IsArchived);
 
                        //remove old items
                        foreach (var oldItem in Devices)
                        {
                            if (token.IsCancellationRequested)
                                return;
 
                                if (filterDevices.Find(x => x.Id == oldItem.Id) == null)
                                Devices.Remove(oldItem);
                        }
 
                        //add or change new items
                        foreach (var newItem in filterDevices)
                        {
                            if (token.IsCancellationRequested)
                                return;
 
                            var elm = Devices.FirstOrDefault(x => x.Id == newItem.Id);
                            if (elm != null)
                            {
                                if (!newItem.Equals(elm))
                                    elm = newItem;
                            }
                            else
                            {
                                Devices.Add(newItem);
                            }
                        }
                    }
                    else
                    {
                        Devices.Clear();
                    }
                }
                cancelTokenSource.Cancel();
            });
 
            await UpdateContextMenu();
        }
Выход из страницы.
C#
1
2
3
4
5
        public override void Deactivate()
        {
            base.Deactivate();
            cancelTokenSource.Cancel();
        }
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
07.10.2021, 03:09
limeniye, у меня нет понимания структуру вашего проекта и поэтому я не понимаю где всё это надо сделать.

Но последовательность такая.
1) Создали экземпляр токена;
2) Его используют все задачи которые может потребоваться отменить;
3) Когда токен отменяются - завершаются все задачи которые его использовали;
4) Токен нужно уничтожить (Dispose). Вот этотм момент не могу сказать точно, когда следует делать. Толи можно сразу после отмены, толи надо дождаться завершения всех задача его использующих;
5) Когда потребуется создать следующую очередь задач, то сначала создаётся новый экземпляр токена и им замещается уничтоженный.


Цитата Сообщение от limeniye Посмотреть сообщение
UpdateAllWithCancellionChecker
Для меня этот метод выглядит очень корявым.
У меня очень сильные сомнения, что вы верно используете здесь асинхронность.
Посмотрите что вы делаете:
1) У вас идёт асинхронное получение коллекции devices:
2) Потом вы асинхронно по ней меняете коллекцию (наверное свойство) Devices;
3) Пока изменяете Devices вы производите локировку.

НО! Изменение привязанной коллекции (если вы используете мою реализацию AsyncObsevable) будет происходить в основном потоке.
Вы пишите, что такая реализация не подвешивает GUI.
Следовательно, с высокой долей вероятности, изменение Devices (пункты 2 и 3) происходят очень быстро.
А раз так, то и встраивать в них токен отмены нет смысла.

Скорее всего у вас тормозит метод devicesRepository.GetDevices (пункт 1).
Вы его сделали асинхронным и этого достаточно.
Пункты 2 и 3 надо выполнять уже просто в методе - без задачи, так как они всё равно по факту выполняются в основном потоке.

Попробуйте проверить на тормоза такую реализацию:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
        private async Task UpdateAllWithCancellionChecker()
        {
            var id = currentDevice == null ? Root : currentDevice.Id;
            var devices = await devicesRepository.GetDevices(id);
 
            // await Task.Run(() =>
           // {
                lock (((ICollection)Devices).SyncRoot)
                {
                  // Проверка изменились ли условия запроса
                  // Сюда же можно добавить и проверку токена отмены
                    if (id == (currentDevice == null ? Root : currentDevice.Id) &&
                       ! token.IsCancellationRequested)
                    if (devices != null)
                    {
                        var filterDevices = devices.FindAll(d => !d.IsArchived);
 
                        //remove old items
                        foreach (var oldItem in Devices)
                        {
                      //      if (token.IsCancellationRequested)
                      //          return;
 
                                if (filterDevices.Find(x => x.Id == oldItem.Id) == null)
                                Devices.Remove(oldItem);
                        }
 
                        //add or change new items
                        foreach (var newItem in filterDevices)
                        {
                     //       if (token.IsCancellationRequested)
                     //           return;
 
                            var elm = Devices.FirstOrDefault(x => x.Id == newItem.Id);
                            if (elm != null)
                            {
                                if (!newItem.Equals(elm))
                                    elm = newItem;
                            }
                            else
                            {
                                Devices.Add(newItem);
                            }
                        }
                    }
                    else
                    {
                        Devices.Clear();
                    }
                }
              // Отмена токена приведёт к отмене всех задач его использующих.
              // Это надо делать при смене страницы, например
             //   cancelTokenSource.Cancel();
            //});
 
            await UpdateContextMenu();
        }
Саму коллекцию Devices сделать обычной ObservableCollection, но обязательно в конструкторе задать синхронизацию привязок.
Тогда её можно будет менять из любого потока.
1
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
07.10.2021, 10:54  [ТС]
Элд Хасп, да, Devices это свойство { get; }
Касательно
Скорее всего у вас тормозит метод devicesRepository.GetDevices (пункт 1).
и
Следовательно, с высокой долей вероятности, изменение Devices (пункты 2 и 3) происходят очень быстро.
А раз так, то и встраивать в них токен отмены нет смысла.
Я поставил 2 точки останова в решении без асинхрона, без локировки и без токена отмены на строке 1 и 36:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
                    if (devices != null)
                    {
                        var filterDevices = devices.FindAll(d => !d.IsArchived);
 
                        //remove old items
                        foreach (var oldItem in Devices)
                        {
                            if (token.IsCancellationRequested)
                                return;
 
                                if (filterDevices.Find(x => x.Id == oldItem.Id) == null)
                                Devices.Remove(oldItem);
                        }
 
                        //add or change new items
                        foreach (var newItem in filterDevices)
                        {
                            if (token.IsCancellationRequested)
                                return;
 
                            var elm = Devices.FirstOrDefault(x => x.Id == newItem.Id);
                            if (elm != null)
                            {
                                if (!newItem.Equals(elm))
                                    elm = newItem;
                            }
                            else
                            {
                                Devices.Add(newItem);
                            }
                        }
                    }
                    else
                    {
                        Devices.Clear();
                    }
По какой-то причине, от строки 1 до строки 36 проходит 3 секунды.

Далее я поставил точки остановка на каждый цикл, чтобы проверить — какой самый медленный. Но если отдельно проверить все циклы — то каждый проходит быстро, но если запустить точку останова от строки 1 до 36 — 3 секунды занимает.

Я могу предположить, что проблема в том, что Devices — это коллекция NotifyPropertyChanged свойств и из-за этого может происходить какие-то проблемы (например двойное изменение одного свойства где-то в архитектуре), или что-то в этом роде (сугубо предположение).

Добавлено через 1 час 37 минут
Протестировал следующую запись:

Цитата Сообщение от Элд Хасп Посмотреть сообщение
// await Task.Run(() =>
           // {
                lock (((ICollection)Devices).SyncRoot)
                {
                  // Проверка изменились ли условия запроса
                  // Сюда же можно добавить и проверку токена отмены
                    if (id == (currentDevice == null ? Root : currentDevice.Id) &&
   
Приложение подтормаживает.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16115 / 11236 / 2887
Регистрация: 21.04.2018
Сообщений: 33,036
Записей в блоге: 2
07.10.2021, 11:08
Цитата Сообщение от limeniye Посмотреть сообщение
По какой-то причине, от строки 1 до строки 36 проходит 3 секунды.
Чё-то нереальный какой-то результат.

Уточните:
1) Devices - обычная ObservableCollection и используете синхронизацию привязок к ней?
2) Каковы (примерно) размеры Devices и devices?

Добавлено через 7 минут
Цитата Сообщение от limeniye Посмотреть сообщение
if (devices != null)
Если можете отрегулировать метод devicesRepository.GetDevices, то он не должен возвращать null.
Если данных нет, то он должен возвращать пустую коллекцию.
1
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
07.10.2021, 12:16  [ТС]
Элд Хасп, провёл некоторое тестирование.

Попробовал поставить ObservableCollection, оставить await.Task и локировку.
Результат: подтормаживает. Элементы отображаются уже прогруженными.
C#
1
2
3
4
5
6
ObservableCollection ...
 
await.Task.Run( () => 
{
    lock (...
}
Попробовал поставить ObservableCollection, убрать await.Task и оставить локировку.
Результат: очень странное поведение, подтормаживает, но редко. Элементы отображаются уже прогруженными.
C#
1
2
3
ObservableCollection ...
 
lock (...

Попробовал поставить AsyncObservableCollection, оставить await.Task и локировку.
Результат: не подтормаживает. Элементы прогружаются один за одним.
C#
1
2
3
4
5
6
AsyncObservableCollection ...
 
await.Task.Run( () => 
{
    lock (...
}
Попробовал поставить AsyncObservableCollection, убрать await.Task и оставить локировку.
Результат: страница подрормаживает. А потом элементы отображаются уже прогруженными.
C#
1
2
3
AsyncObservableCollection ...
 
lock (...
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Каковы (примерно) размеры Devices и devices?
Даже примерно сказать не могу.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
07.10.2021, 12:16
Помогаю со студенческими работами здесь

Данные об отделах ( процент заполения )
Добрый вечер, помогите сделать запрос *Данные об отделах, у которых процент заполнения штата меньше 80* Файл снизу. Заранее спасибо :)

DataGridView1 не соблюдается условие заполения
Это окно редактирования документа в новой форме. Когда нажимаешь на button1, должна заполнится dataGridView1 согласно тому номеру который...

Почему CancellationToken не сбрасывает Token
Пытаюсь сбросить операцию ConnectAsync токеном. Но при выставлении значения 1 сек операцию не сбрасывается. В результате выполнения...

Проверка на правильность заполения полей
Как проверить форму на правильность заполнения полей...Например мне нужно проверить правильно ли у меня заполнено поле Емайл...Как это...

Отправка формы после проверки заполения reCAPTCHA
Можно ли сделать отправку формы после того, как reCAPTCHA примет статус &quot;Галочка&quot;?


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru