Списка фильтрация асинхронных, отменяя предыдущие задач на обновления фильтра


Я пытаюсь придумать хороший способ фильтрации ListBox асинхронно, чтобы сохранить пользовательский интерфейс отзывчивый. Фильтрация получает выстрелил в асинхронном режиме каждый раз текст фильтра. Это должен отменить все предыдущие операции фильтрации, поэтому обновления для отфильтрованного списка не конфликт.

Кажется, что список CancellationTokenSource объекты нужны, так что каждая задача может быть отменен самостоятельно.

Ниже-лучшее, что мне удалось придумать до сих пор. Мне кажется, что там должно быть лучше способа сделать это.

Модель Представления - MainPresenter.в CS

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncListBoxFilter.ViewModels
{
    public sealed class MainPresenter : INotifyPropertyChanged
    {
        private const int ITEM_COUNT = 1000;
        private static readonly Random Rand = new Random();

        private readonly ICollection<CancellationTokenSource> _cancellationTokenSources
            = new List<CancellationTokenSource>();

        private readonly ICollection<string> _allItems;
        private string _filterString;

        public event PropertyChangedEventHandler PropertyChanged;

        public MainPresenter()
        {
            _allItems = new List<string>(
                Enumerable.Range(0, ITEM_COUNT)
                    .Select(_ => CreateRandomString()));
            FilteredItems = new ObservableCollection<string>(_allItems);
        }

        public ObservableCollection<string> FilteredItems { get; private set; }

        public string FilterString
        {
            get => _filterString;
            set
            {
                _filterString = value;
                OnPropertyChanged();
                foreach (var source in _cancellationTokenSources)
                    source.Cancel();
                Task.Run(FilterAsync);
            }
        }

        private async Task FilterAsync()
        {
            var source = new CancellationTokenSource();
            _cancellationTokenSources.Add(source);
            var cancellationToken = source.Token;

            var filterString = FilterString; // Take a copy in case it changes
            var filtered = new ObservableCollection<string>();
            await Task.Run(() =>
            {
                var token = cancellationToken;

                foreach (var item in _allItems)
                {
                    if (token.IsCancellationRequested)
                        break;

                    Thread.Sleep(1); // Slow down for testing

                    if (item.Contains(filterString ?? string.Empty))
                        filtered.Add(item);
                }
            }, cancellationToken);

            if (cancellationToken.IsCancellationRequested)
            {
                Console.WriteLine($"'{filterString}' Cancelled");
            }
            else
            {
                FilteredItems = filtered;
                OnPropertyChanged(nameof(FilteredItems));
                Console.WriteLine($"'{filterString}' Success");
            }
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private static string CreateRandomString()
        {
            char CreateRandomChar() => (char) (65 + Rand.Next(26));
            return new string(Enumerable.Range(0, Rand.Next(3, 10))
                .Select(_ => CreateRandomChar()).ToArray());
        }
    }
}

Вид - Файл MainWindow.язык XAML

<Window x:Class="AsyncListBoxFilter.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ViewModels="clr-namespace:AsyncListBoxFilter.ViewModels"
        Title="Async ListBox Filter"
        Height="600" Width="800"
        WindowStartupLocation="CenterScreen">

    <Window.DataContext>
        <ViewModels:MainPresenter />
    </Window.DataContext>

    <DockPanel>
        <TextBox DockPanel.Dock="Top"
                 Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}"
                 Margin="5" />
        <ListBox ItemsSource="{Binding FilteredItems}" Margin="5" />
    </DockPanel>

</Window>


476
3
задан 11 апреля 2018 в 02:04 Источник Поделиться
Комментарии
3 ответа

Это решение не убедить меня, потому что...



public string FilterString
{
get => _filterString;
set
{
_filterString = value;
OnPropertyChanged();
foreach (var source in _cancellationTokenSources)
source.Cancel();
Task.Run(FilterAsync);
}
}

Не так много работы в сеттер. Это не самое подходящее место для этого. Вы должны реализовать его как команду FilterCommand и огонь ее, когда текст меняется.


В CancellationTokenSource должны быть утилизированы. Вы не делаете это.


Вы не используете Thread.Sleep при работе с async/await выкройка, потому что вы не хотите ставить всю ветку спать. То, что вы хотите Task.Delay.



var token = cancellationToken;

Что делает эта строка есть?



FilteredItems = filtered;
OnPropertyChanged(nameof(FilteredItems));

В FilterAsync не должно вызывать событие изменения свойства. Задание должно быть все, что он делает. С другой стороны, если его тип является ObservableCollection затем вы должны использовать это для обновления или превратить его в обычный список, потому что это смущает, когда у вас наблюдаемый, что вы не используете в качестве таковых.



ICollection<CancellationTokenSource> _cancellationTokenSources

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

3
ответ дан 11 апреля 2018 в 05:04 Источник Поделиться

Вы могли бы хотеть рассмотреть, что _filterString и стоимость может быть одинаковой, в каком случае вы хотите, чтобы остальные сеттер логики для запуска? Причин может быть много работы для очень мало пользы.

if(_filterString.Equals(value)) return;
OnPropertyChanged();
...

1
ответ дан 12 апреля 2018 в 02:04 Источник Поделиться

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

0
ответ дан 12 апреля 2018 в 07:04 Источник Поделиться