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

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

06.10.2021, 14:07. Показов 8443. Ответов 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
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
10.10.2021, 02:46
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от escoult Посмотреть сообщение
Если бы это был IEnumerable<T>, то вы могли бы обрабатывать по одному элементу.
Разницы никакой нет.
И кроме прочего, возврат по одному элементу с DbContext приведёт к отдельному запросу к БД для каждого элемента.
Это точно не стоит делать.

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

Добавлено через 2 минуты
Цитата Сообщение от escoult Посмотреть сообщение
Нет, yield как раз и должен где-то там быть по идее.
DbContext возвращает не IEnumerable, а IQueryable, который действителен только для этого контекста.
За его границами из него невозможно получить данные.
0
1522 / 508 / 126
Регистрация: 09.01.2018
Сообщений: 1,541
10.10.2021, 02:58
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Разницы никакой нет.
Та ну...

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
    class Program
    {
        static void Main(string[] args)
        {
            var sc = new SomeCollection();
 
            //плавно по одному, все как надо
            var list = sc.GetAllEnumerable();
 
            //ждем пока не соберется весь список
            //var list = sc.GetAllList();
 
            foreach (var item in list)
            {
                Console.WriteLine(item);
            }
        }
    }
 
    public class SomeCollection
    {
        public IEnumerable<int> GetAllEnumerable()
        {
            for (int i = 0; i < 250; i++)
            {
                Thread.Sleep(10);
                yield return i;
            }
        }
 
        public List<int> GetAllList()
        {
            var list = new List<int>();
            for (int i = 0; i < 250; i++)
            {
                Thread.Sleep(10);
                list.Add(i);
            }
            return list;
        }
    }
Цитата Сообщение от Элд Хасп Посмотреть сообщение
И кроме прочего, возврат по одному элементу с DbContext приведёт к отдельному запросу к БД для каждого элемента.
Не, зачем запрашивать по одному. Запросить можно сразу все, либо диапазон. Но потом картинки к объектам присобираются, преобразования разные начинаются, вот тут можно возвращать по одному.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
10.10.2021, 09:06
Цитата Сообщение от escoult Посмотреть сообщение
Запросить можно сразу все, либо диапазон. Но потом картинки к объектам присобираются
Это всё происходит мгновенно, на фоне создания представления для одного элемента.
Распараллелить надо именно создание представлений, ну и оптимизировать само представление.
Выше были предоставлены тайминги исполнения: пост #41, пост #45 и пост #54.
Посмотрите сами.

Добавлено через 8 минут
Цитата Сообщение от escoult Посмотреть сообщение
Та ну...
Ваш тестовый код не имеет отношения к обсуждаемой задаче.

Как вы его можете применить к такому (по сути) методу:
C#
1
2
3
4
5
6
7
8
9
10
public async Task<List<DeviceViewModel>> GetDevices(string id)
{
     return await Task.Run(()=>
     {
          using(var db = new DbContext())
          {
               return db.Devices.Where(d => d.id==id).ToList();
          }
     });
}
1
1522 / 508 / 126
Регистрация: 09.01.2018
Сообщений: 1,541
10.10.2021, 09:37
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Ваш тестовый код не имеет отношения к обсуждаемой задаче.
Это не тестовый код, это пример, иллюстрирующий, что в определенных случаях есть разница.
Я же не знаю деталей, как там реализован этот метод GetDevices, что именно он делает по пути, может он грузит картинки откуда то с сервера, может еще что делает. Задержки то есть. Поэтому остаются только предположения.

Добавлено через 17 минут
И потом, насколько я понимаю, если UI тратит много времени на отображение одной картинки, то на 10 картинок он потратит в 10 раз больше. Значит они будут медленно, по одной, с ощутимой задержкой появляться, а не все разом после длительной задержки.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
10.10.2021, 10:28
Цитата Сообщение от escoult Посмотреть сообщение
Значит они будут медленно, по одной, с ощутимой задержкой появляться, а не все разом после длительной задержки
Это зависит от того, как собирается коллекция для представления.
Именно с этим нюансом и пытаемся разобраться.

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

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

Цитата Сообщение от escoult Посмотреть сообщение
Я же не знаю деталей, как там реализован этот метод GetDevices, что именно он делает по пути, может он грузит картинки откуда то с сервера, может еще что делает. Задержки то есть
При получении списка из этого метода задержек нет.
С местом где возникают задержке разобрались в начале темы.
Что подтверждают тайминги на которые я вам дал ссылки.

Добавлено через 3 минуты
Цитата Сообщение от escoult Посмотреть сообщение
И потом, насколько я понимаю, если UI тратит много времени на отображение одной картинки
Сейчас там просто Frame:
Цитата Сообщение от limeniye Посмотреть сообщение
Также, в качестве элемента "Прямоугольник" я использую Frame (то же самое что и Border в WPF).
Вот мне это тоже совершенно не понятно.
Почему простой прямоугольник создаётся почти 50 мс?
Какая-то особенность Xamarin?
Или тестовое устройство очень медленное?
1
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
10.10.2021, 14:42  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Второй разве обязательный?
Если да, то передайте null.
C#
1
_synchronizationContext.Send(() => base.OnCollectionChanged(e), null);
Delegate 'SendOrPostCallback' does not take 0 arguments

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Есть такая вероятность?
там токен отмены есть, но токен отмены может сработать на 1 миллисекунду позже, например
и тогда у меня IsCancellationRequested будет true, но уже после проверки, а следовательно следующее условие выполнится. Я думаю такое вероятно, поэтому локировку я оставлю.
C#
1
2
3
4
5
6
7
8
9
10
11
12
if (filterDevices.Count == 0)
{
    if (token.IsCancellationRequested) // тут false
        return;
 
    //а тут IsCancellationRequested  уже true, но уже поздно.
 
    //и этот код выполнится
    Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices.Clear()));
 
    return;
}

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Сейчас там просто Frame:
да, абсолютно верно, сейчас там только Fram'ы.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Почему простой прямоугольник создаётся почти 50 мс?
Очень странное поведение программы. Я сам не понимаю, отловить причину очень сложно.
Кроме как методом "тыка" я уж не знаю как ещё.

Лично я грешу на сам INotyfiPropertyChanged -- реализацию. Возможно она, из-за изобилия событий OnPropertyChanged, которые даже не используются, немного нагружает UI каким-то образом (уж из крайности в крайность).

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Или тестовое устройство очень медленное?
Тестирую на Android. OnePlus 7T pro, вроде никогда не подводил, тормозов не наблюдалось, я сомневаюсь что дело в железе.

Добавлено через 11 минут
На данный момент вот такая реализация
Кликните здесь для просмотра всего текста

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
59
60
61
62
63
64
65
66
67
68
        private Task UpdateDevicesWithCancellationTokenChecker(CancellationToken token) => Task.Run(() =>
        {
            var id = currentDevice == null ? Root : currentDevice.Id;
            
            var devices = Task.Run(async() => await devicesRepository.GetDevices(id)).Result;
 
            lock (((ICollection)Devices).SyncRoot)
            {
                if (devices.Count <= 0)
                {
                    if (token.IsCancellationRequested)
                        return;
 
                    Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices.Clear()));
                    return;
                }
                       
 
                var filterDevices = devices.Where(d => !d.IsArchived).ToDictionary(d => d.Id);
                if (filterDevices.Count == 0)
                {
                    if (token.IsCancellationRequested)
                        return;
 
                    Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices.Clear()));
 
                    return;
                }
 
 
                //remove old items and change new items
                for (int i = Devices.Count - 1; i >= 0; i--)
                {
                    var index = i;
 
                    if (token.IsCancellationRequested)
                        return;
 
                    var device = Devices[index];
                    if (filterDevices.TryGetValue(device.Id, out var newDevice))
                    {
                        Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices[index] = newDevice));
                        filterDevices.Remove(device.Id);
                    }
                    else
                    {
                        Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices.RemoveAt(index)));
                    }
                }
 
                //add new items
                if (filterDevices.Count != 0)
                {
                    Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => 
                    { 
                        foreach (var newItem in filterDevices.Values)
                        {
                            if (token.IsCancellationRequested)
                                return;
 
                            
                            Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices.Add(newItem)));                            
                        }
                    }));
                }
            }
           UpdateContextMenu();
        });



Если я правильно понял, для AsyncObservableCollection я меняю
private Task UpdateDevicesWithCancellationTokenChecker(CancellationToken token) => Task.Run(() =>
на
private async Task UpdateDevicesWithCancellationTokenChecker(CancellationToken token) => await Task.Run(async() =>

Оставляю Device.InvokeOnMainThreadAsync, а так как он асинхронный и в локировке, то его запись оставляю без изменений вот так
Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => 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
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
59
60
61
62
63
64
65
66
67
68
69
        private async Task UpdateDevicesWithCancellationTokenChecker(CancellationToken token) => await Task.Run(async() =>
        {
            
            var id = currentDevice == null ? Root : currentDevice.Id;
            
            var devices = await devicesRepository.GetDevices(id);
 
            lock (((ICollection)Devices).SyncRoot)
            {
                if (devices.Count <= 0)
                {
                    if (token.IsCancellationRequested)
                        return;
 
                    Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices.Clear()));
                    return;
                }
                       
 
                var filterDevices = devices.Where(d => !d.IsArchived).ToDictionary(d => d.Id);
                if (filterDevices.Count == 0)
                {
                    if (token.IsCancellationRequested)
                        return;
 
                    Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices.Clear()));
 
                    return;
                }
 
 
                //remove old items and change new items
                for (int i = Devices.Count - 1; i >= 0; i--)
                {
                    var index = i;
 
                    if (token.IsCancellationRequested)
                        return;
 
                    var device = Devices[index];
                    if (filterDevices.TryGetValue(device.Id, out var newDevice))
                    {
                        Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices[index] = newDevice));
                        filterDevices.Remove(device.Id);
                    }
                    else
                    {
                        Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices.RemoveAt(index)));
                    }
                }
 
                //add new items
                if (filterDevices.Count != 0)
                {
                    Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => 
                    { 
                        foreach (var newItem in filterDevices.Values)
                        {
                            if (token.IsCancellationRequested)
                                return;
 
                            
                            Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices.Add(newItem)));
                        }
                    }));
                }
            }
           await UpdateContextMenu();
        });
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
10.10.2021, 15:49
Цитата Сообщение от limeniye Посмотреть сообщение
Delegate 'SendOrPostCallback' does not take 0 arguments
Я думал там Action принимает.
Тогда так:
C#
1
2
3
_synchronizationContext.Send(_ => base.OnCollectionChanged(e));
// или
_synchronizationContext.Send(_ => base.OnCollectionChanged(e), null);
Добавлено через 3 минуты
Цитата Сообщение от limeniye Посмотреть сообщение
Лично я грешу на сам INotyfiPropertyChanged -- реализацию. Возможно она, из-за изобилия событий OnPropertyChanged
Нет.
Если на них подписки нет (то ест нет отображения этих свойств), то поднятие в других потоках PropertyChanged никак не влияет на GUI.

Добавлено через 3 минуты
Цитата Сообщение от limeniye Посмотреть сообщение
Тестирую на Android. OnePlus 7T pro, вроде никогда не подводил, тормозов не наблюдалось, я сомневаюсь что дело в железе.
Попробуйте создать тестовое приложение, в котором по кнопке добавится несколько сот элементов в коллекцию (простых строк или случайных чисел).
И посмотрите как быстро отработает GUI отражающий эту коллекцию.
Для элемента задайте такое же представление как в вашем приложении.

Если тормозов не будет, то придётся перелопачивать ваше приложение и искать в чём причина тормозов.

Добавлено через 4 минуты
Цитата Сообщение от limeniye Посмотреть сообщение
там токен отмены есть, но токен отмены может сработать на 1 миллисекунду позже, например
и тогда у меня IsCancellationRequested будет true, но уже после проверки
чё-то каша какая-то.
Тогда вам нужно при изменении условия поиска id = currentDevice == null ? Root : currentDevice.Id, тоже отменять предыдущий токен, создавать новый и вызывать метод для нового условия с новым токеном.
1
1522 / 508 / 126
Регистрация: 09.01.2018
Сообщений: 1,541
10.10.2021, 16:02
Цитата Сообщение от limeniye Посмотреть сообщение
Кроме как методом "тыка" я уж не знаю как ещё.
Для таких случаев пишутся тесты, гадать не продуктивно. Запрос данных от репозитория, от узла перед ним, фейковый репозиторий, который к серверам не обращается и берет тестовые данные из локальной коллекции, и т.д.
2
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
10.10.2021, 16:07
Цитата Сообщение от limeniye Посмотреть сообщение
Оставляю Device.InvokeOnMainThreadAsync, а так как он асинхронный и в локировке, то его запись оставляю без изменений вот так
Task.Run(async () => await Device.InvokeOnMainThreadAsync(() => Devices.Clear()));
В отношении локировки - это бессмыслица.
Task.Run - просто ставит задачу в очередь пула.
А когда выполнится эта задача - неизвестно.
Конечно, в большинстве случаев это происходит достаточно быстро.
Но это не гарантированно.
Вполне возможно что задача начнётся выполняться уже после прекращения локировки или даже после выхода из вашего основного метода.

Внутри лока должен быть только синхронный код.

В этом случае можно сделать так:
C#
1
2
var task = Task.Run(...);
task.Wait();
Но это бессмысленно поскольку, фактически, равносильно отсутствию задачи и прямому, синхронному исполнению метода.

Цитата Сообщение от limeniye Посмотреть сообщение
И общий код следующий
Чё-то слишком много вложенных асинков.
С учётом использования AsyncObservableCollection, наверное всё же так:
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
        private async Task UpdateDevicesWithCancellationTokenChecker(CancellationToken token)
        {
            // функция проверки изменения условия и отмены токена
            boll CheckCancel() => (id != currentDevice == null ? Root : currentDevice.Id) ||
                         token.IsCancellationRequested;
 
            var id = currentDevice == null ? Root : currentDevice.Id;
            
            var devices = await devicesRepository.GetDevices(id);
 
            await Task.Run(()=>
        {
            lock (((ICollection)Devices).SyncRoot)
            {
                if (devices.Count <= 0)
                {
                    if (CheckCancel())
                        return;
 
                    Devices.Clear();
                    return;
                }
                       
 
                var filterDevices = devices.Where(d => !d.IsArchived).ToDictionary(d => d.Id);
                if (filterDevices.Count == 0)
                {
                    if (CheckCancel())
                        return;
 
                    Devices.Clear();
 
                    return;
                }
 
 
                //remove old items and change new items
                for (int i = Devices.Count - 1; i >= 0; i--)
                {
                    var index = i;
 
                    if (CheckCancel())
                        return;
 
                    var device = Devices[index];
                    if (filterDevices.TryGetValue(device.Id, out var newDevice))
                    {
                        Devices[index] = newDevice;
                        filterDevices.Remove(device.Id);
                    }
                    else
                    {
                        Devices.RemoveAt(index);
                    }
                }
 
                //add new items
                if (filterDevices.Count != 0)
                {
                        foreach (var newItem in filterDevices.Values)
                        {
                            if (CheckCancel())
                                return;
 
                            
                            Devices.Add(newItem);
                        }
                }
            }
       });
  
           await UpdateContextMenu();
        }
Добавлено через 28 секунд
Пишу в редакторе сообщения - могут быть ошибки.
1
1522 / 508 / 126
Регистрация: 09.01.2018
Сообщений: 1,541
10.10.2021, 17:18
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Ваш тестовый код не имеет отношения к обсуждаемой задаче.
Как вы его можете применить к такому (по сути) методу:

C#
1
2
3
4
5
6
7
8
9
10
public async Task<List<DeviceViewModel>> GetDevices(string id)
{
     return await Task.Run(()=>
     {
          using(var db = new DbContext())
          {
               return db.Devices.Where(d => d.id==id).ToList();
          }
     });
}
Ну не так же ж оно делается.
Вот например:

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
    public class DeviceService : IDeviceService
    {
        private readonly InMemoryContext _context;
        private readonly IImageService _imageService;
 
        //dependency injection
        public DeviceService(InMemoryContext context, IImageService imageService)
        {
            _context = context;
            _imageService = imageService;
        }
 
        public async IAsyncEnumerable<DeviceModel> GetAsync(int categoryId)
        {
            var data = _context.Devices.Where(d => d.CategoryId == categoryId);
            foreach (var item in data)
            {
                var address = await _imageService.GetImageAsync(item.ImageUri);
                var model = new DeviceModel { 
                    Id = item.Id, 
                    CategoryId = item.CategoryId,
                    ImageAddress = address };
                yield return model;
            }
        }

Даже если в методе GetImageAsync выставить Task.Delay(50), вывод все равно будет плавным.
2
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
10.10.2021, 18:33
Цитата Сообщение от escoult Посмотреть сообщение
Даже если в методе GetImageAsync выставить Task.Delay(50), вывод все равно будет плавным.
А зачем там его вставлять?

Я же вам давал ссылки на тайминги исполнения.
Задержка получаются не при извлечении данных из БД (ил откуда там они получаются? - это не важно), а при добавлении извлечённых данных в коллекцию, привязанную в Представлении.

Кроме того InMemory поможет при частых обращениях к БД за одними и теми же данными, которые не изменяются внешне.
Но если при каждом обращении требуются разные данные или эти данные постоянно меняются извне, то чем здесь может помочь InMemory?
Всё равно при каждом запросе будет происходить запрос к реальной БД за реальными данными.

По коду же понятно, что есть коллекция Devices отражающая какие-то данные.
В методе делается очередной снимок (кадр) данных и потом по этому снимку нужно обновить Devices.
Допустим, в очередном кадре нет какого-то элемента из Devices и значит нужно его удалить.
НО!!! чтобы понять, что его нет в кадре, нужен целиком этот кадр.
Что толку от последовательного получения отдельных элементов кадра?

Добавлено через 2 минуты
escoult, я нисколько не спорю с вами, что есть случаи когда лучше реализовывать с использованием InMemory и IEnumerable.
Но конкретно эта задача не относится к таким случаям.
Посмотрите внимательнее на код и объяснения в топике.
Там же сразу понятно, что и для чего делается.
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
10.10.2021, 18:39  [ТС]
Элд Хасп, тут ничего не понял

C#
1
2
3
            // функция проверки изменения условия и отмены токена
            boll CheckCancel() => (id != currentDevice == null ? Root : currentDevice.Id) ||
                         token.IsCancellationRequested;
Во-первых, там же после проверки позвращает либо Root либо Id -- это тип Guid ? Root : currentDevice.Id.
А функция возращает тип boll, тогда как понять какой должен быть результат: Root или Id.
Во-вторых, Guid -- это структура и как я понял, там == null -- излишнее, ибо оно относится к id в том числе, как я понимаю, а id -- не объект типа DeviceViewModel -- это Guid.
0
1522 / 508 / 126
Регистрация: 09.01.2018
Сообщений: 1,541
10.10.2021, 18:47
Цитата Сообщение от Элд Хасп Посмотреть сообщение
А зачем там его вставлять?
Симуляция получения картинки от сервера или какой то другой работы.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
то чем здесь может помочь InMemory
Это пример. Я не захотел создавать БД, поэтому использовал InMemory.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Задержка получаются не при извлечении данных из БД (ил откуда там они получаются? - это не важно)
Я в этом совершенно не уверен. Картинки откуда берутся? И почему их требуется обновлять? Значит они изменяются.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
НО!!! чтобы понять, что его нет в кадре, нужен целиком этот кадр.
Что толку от последовательного получения отдельных элементов кадра?
Ну получили вы всю коллекцию целиком, как определите какой элемент требуется удалить, а какой добавить по цельной коллекции? Коллекцию же будете перебирать по одному элементу. Но только вам придется сначала подождать пока она полностью соберется. А получая элементы последовательно вы ожидаете ровно один элемент, остальные в это время собираются.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
10.10.2021, 19:05
Цитата Сообщение от limeniye Посмотреть сообщение
Во-первых, там же после проверки позвращает либо Root либо Id -- это тип Guid ? Root : currentDevice.Id.
Тип не знал какой.
Но суть в другом. При входе в метод вы получили какой-то id.
Именно он определяет условия запроса.
При изменении условий запроса (то есть этого id) нужно прервать метод или нет?
Если нужно то тогда надо проверять сохранённый id на соответствие с текущими условиями.
Если это не нужно, или если у вас это уже обрабатывается при отмене токена, то удалите этот метод и проверяйте только токен.

Цитата Сообщение от limeniye Посмотреть сообщение
А функция возращает тип boll, тогда как понять какой должен быть результат: Root или Id.
Проверяются условия прерывания метода.
если id не равен ... или токен отменён, то возвращается true и значит надо прервать метод.

Цитата Сообщение от limeniye Посмотреть сообщение
Во-вторых, Guid -- это структура и как я понял, там == null -- излишнее
Там же не Guid проверяется, а currentDevice
Вот добавил скобки, чтобы вам было понятно:
C#
1
2
3
            // функция проверки изменения условия и отмены токена
            boll CheckCancel() => (id != (currentDevice == null ? Root : currentDevice.Id)) ||
                         token.IsCancellationRequested;

Цитата Сообщение от escoult Посмотреть сообщение
Я в этом совершенно не уверен. Картинки откуда берутся?
НЕТУ ТАМ КАРТИНОК!
Я же писал вам уже, что код тормозит на простых Frame, со слов TC.

limeniye, напишите что у вас выводится в представлении элемента.
Есть там картинки или нет?
Лучше ещё бы и скрин выложили.

Добавлено через 4 минуты
Цитата Сообщение от escoult Посмотреть сообщение
Ну получили вы всю коллекцию целиком, как определите какой элемент требуется удалить, а какой добавить по цельной коллекции?
В коде же есть всё.
Последовательно проверяется Devices и каждый элемент ищется в полученном кадре
Если его нет в кадре, то он удаляется из Devices.

53-я строка:
C#
46
47
48
49
50
51
52
53
54
                    if (filterDevices.TryGetValue(device.Id, out var newDevice))
                    {
                        Devices[index] = newDevice;
                        filterDevices.Remove(device.Id);
                    }
                    else
                    {
                        Devices.RemoveAt(index);
                    }
Цитата Сообщение от escoult Посмотреть сообщение
Но только вам придется сначала подождать пока она полностью соберется
Коллекция Devices УЖЕ СОБРАНА.
Ничего ждать не надо.
Надо только обновить её по очередному кадру.
0
1522 / 508 / 126
Регистрация: 09.01.2018
Сообщений: 1,541
10.10.2021, 19:15
Цитата Сообщение от Элд Хасп Посмотреть сообщение
НЕТУ ТАМ КАРТИНОК!
Я же писал вам уже, что код тормозит на простых Frame, со слов TC.
Нет потому что он их не отображает. Но метод, который возвращает коллекцию Devices их по прежнему получает. И с его слов (я внимательно всю тему не читал, так что сорри, если ошибся) получает он их от сервера. А что в этом методе, он не знает, он туда не заглядывал.

Добавлено через 1 минуту
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Коллекция Devices УЖЕ СОБРАНА.
Из чего она собрана и когда собралась если вы только вызвали метод, который ее создает?

Добавлено через 8 минут
Если бы, к примеру ТС написал фейковый репозиторий с готовой коллекцией, без картинок и подставил бы его вместо основного репозитория, можно было бы твердо сказать да-нет.
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
10.10.2021, 19:28  [ТС]
Элд Хасп, касательно метода, приложенного тут: очень быстро прогружает элементы один за одним, но вот если возвращаю отображение картинки -- прогружает также один за одним, но весь список прогружает дольше.

То есть я ожидаю такой результат: все элементы прогружает также быстро, как и без картинки, но картинка прогружается асинхронно для каждого элемента. А получается, что картинка как-будто прогружается вместе с каждым элементом.
Сильно урежу код, ибо тут его очень много, многие методы придумаю "от балды", чисто для понимания структуры класса.

Отображение картинки следующее:
срабатывает событие PropertyChanged кастомного свойства EntityProperty, туда, как я понимаю, передают тот самый объект DeviceViewModel, внутри которого есть Guid-id картинки.
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        public static readonly BindableProperty EntityProperty = BindableProperty.Create(nameof(Entity), typeof(object), 
            typeof(EntityImage), propertyChanged: OnEntityChanged);
 
 
        public object Entity
        {
            get
            {
                return GetValue(EntityProperty);
            }
            set
            {
                SetValue(EntityProperty, value);
            }
        }
 
        private static void OnEntityChanged(BindableObject bindable, object oldValue, object newValue)
        {
            FirstMethod();
            SecondMethod();
            //...
        }
Этот класс -- это код-бихайнд .xaml страницы, а эта страница -- это и есть отображение изображения.
И в ней меня очень сильно смущают следующие методы:
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
        private void CleanStatusIcon()
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                StatusIconView.Content = new Image();
            });
        }
 
        private void CleanStatus()
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                StatusView.BackgroundColor = Color.Transparent;
            });
        }
 
 
        private void SomeMethod()
        {
                Device.BeginInvokeOnMainThread(() =>
                {
                    EntityImageView.Content = _entityRecolorOperation.CanvasView;
                });
        }
 
        private void GetStatus(Color color)
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                StatusView.BackgroundColor = color;
                StatusView.IsVisible = true;
            });
        }
Как видите везде Device.BeginInvokeOnMainThread -- добавление в пулл, как я понял. А не его асинхронный аналог.


Все методы, кроме следующего, синхронные void

C#
1
2
3
4
5
6
7
8
9
        private async Task<Stream> GetDocument(Guid documentId)
        {
            var photoDocument = await Task.Run(() => _simpleClient.Documents.GetDocument(documentId));
            if (photoDocument != null)
            {
                return photoDocument;
             }
            return null;
        }
И как я понимаю var photoDocument = await Task.Run(() => _simpleClient.Documents.GetDocument(documentId)); вот тут по Guid id-шнику отправляется запрос на получение фото.

Сам же метод вызывается следующим образом:
C#
1
2
3
4
5
        public void GetPersonImage(Entity entity)
        {
            //тут ещё код, а потом это:
            Task.Run(() => GetDocument(documentId));
        }
Какая-то каша, для меня ;(
Может Вы понимаете, почему так. Да и вообще, там объект приходит в событие и из события запускаются другие методы, в которых Device.BeginInvokeOnMainThread(() и Task.Run(() => GetDocument(documentId)); запускается без async await

Добавлено через 4 минуты
Я уже пытался это асинхронизировать -- но результата это абсолютно никакого не дело.

Добавлено через 7 минут
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Тип не знал какой.
где-то там я писал, почему не делаю эту проверку: потому что не понял как сравнить эти типы.
Цитата Сообщение от limeniye Посмотреть сообщение
Тут же хотел бы уточнить: currentDevice -- это какой-то объект какого-то класса, а id -- это обычно int, в моём случае Guid. Не могу сравнивать эти 2 типа.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Именно он определяет условия запроса.
да, абсолютно верно

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Если это не нужно, или если у вас это уже обрабатывается при отмене токена, то удалите этот метод и проверяйте только токен.
Я понял посыл -- допилю.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
10.10.2021, 19:31
Цитата Сообщение от escoult Посмотреть сообщение
Из чего она собрана и когда собралась если вы только вызвали метод, который ее создает?
Это частный случай.
Метод рассчитан именно на обновление коллекции, а не на её замену.
В само начале - это действительно полная запись кадра в коллекцию.
Но записываются УЖЕ ГОТОВЫЕ элементы.
Записываются по одному.
Для каждого создаётся своё событие CollectionChanged.
И для каждого элемента строится отдельно своё представление.

И тормоза именно в построении этого представлении, а не в том как получается кадр.
С получением кадра всё Ок, ничего там оптимизировать не нужно.

Цитата Сообщение от limeniye Посмотреть сообщение
но вот если возвращаю отображение картинки -- прогружает также один за одним, но весь список прогружает дольше
Не отображение картинки, а, как я понял, GUID картинки.
Если вы в XAML убираете представление для этого GUID, то всё нормально, без лагов работает?

Цитата Сообщение от limeniye Посмотреть сообщение
Сам же метод вызывается следующим образом:...
.....
Какая-то каша...
Не каша, а какая-то пурга полная.
Метод GetDocument возвращает Stream, который отправляется прямиком ... в мусор.
Никто этот Stream не использует.

Мне, кажется, что если вы закомментарите тело метода GetPersonImage, то ничего измениться не должно.
Ну, или вы не тот код выложили.
0
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
10.10.2021, 19:36  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
НЕТУ ТАМ КАРТИНОК!
Я же писал вам уже, что код тормозит на простых Frame, со слов TC.
Да, до этого тестировалось без картинок. После последнего исправления от Элд Хасп -- элементы стали прогружаться один за одним очень быстро
Цитата Сообщение от Элд Хасп Посмотреть сообщение
C#
1
2
3
4
5
6
7
8
private async Task UpdateDevicesWithCancellationTokenChecker(CancellationToken token)
 {
 // функция проверки изменения условия и отмены токена
 boll CheckCancel() => (id != currentDevice == null ? Root : currentDevice.Id) ||
 token.IsCancellationRequested;
var id = currentDevice == null ? Root : currentDevice.Id;
var devices = await devicesRepository.GetDevices(id);
//...
Поэтому я добавил отображение картинок и результат следующий: список прогружается также: 1 элемент за один элементом, но уже вместо 3-7 секунд до последнего элемента -- 20-25 секунд до последнего элемента.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16116 / 11237 / 2887
Регистрация: 21.04.2018
Сообщений: 33,038
Записей в блоге: 2
10.10.2021, 19:42
Цитата Сообщение от limeniye Посмотреть сообщение
потому что не понял как сравнить эти типы
В GUID переопределён метод Equals(object).
Поэтому обычного сравнения операторами равенства-неравенства достаточно.

Добавлено через 2 минуты
Цитата Сообщение от limeniye Посмотреть сообщение
Поэтому я добавил отображение картинок и результат следующий:...
Это уже проблема, оптимизации Представления и к этому методу она не имеет отношения.

Надо разбираться каким образом в Представлении по полученному GUID извлекается картинка.

Добавлено через 3 минуты
limeniye, картинки какой элемент представляет? Image?
Если да то ему ImageSource нужны.
Их надо готовить в отдельных задачах, замораживать и потом передавать в Image.
1
 Аватар для limeniye
1182 / 624 / 160
Регистрация: 19.04.2018
Сообщений: 2,923
10.10.2021, 19:49  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
но вот если возвращаю отображение картинки -- прогружает также один за одним, но весь список прогружает дольше
Не отображение картинки, а, как я понял, GUID картинки.
Нет, Вы не правильно меня поняли, там правильно я написал "если возвращаю отображение картинки".

То есть было так
XML
1
<!--controls:EntityImage Entity="{Binding .}" Style="{StaticResource ListItemImageStyle}"/-->
а я "вернул отображение картинки"
XML
1
<controls:EntityImage Entity="{Binding .}" Style="{StaticResource ListItemImageStyle}"/>
Добавлено через 4 минуты
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Если вы в XAML убираете представление для этого GUID, то всё нормально, без лагов работает?
если закомментировать эту строку, то страница открывается без лагов, а потом элементы списка прогружаются 1 за одним в течении 7 секунд, при этом я могу скроллить список.


Если добавляю эту строку: страница открывается без лагов, элементы добавляются один за одним, список тоже могу скроллить, но вот заполнение списка занимает уже не 7 секунд, а 25.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
10.10.2021, 19:49
Помогаю со студенческими работами здесь

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

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

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

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

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


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

Или воспользуйтесь поиском по форуму:
80
Ответ Создать тему
Новые блоги и статьи
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