Я разрабатываю пользовательский интерфейс многоплатформенного приложения (MAUI). Я использую инструментарий сообщества, чтобы привязать команду к событию TextChanged панели поиска. В двух словах, эта команда ищет элементы в List<string>, который начинается со свойства searchbar.Text. Соответствующий код ниже:
// Occurring methods and variables
[ObservableProperty]
List<string> selectedTopicNames;
CancellationTokenSource cts = new();
List<string> GetNames() => topics.TopicsData.Select(element => element.Name).ToList();
// The actual method
[RelayCommand]
async Task PerformSearchAsync(string keyWord)
{
await cts.CancelAsync();
cts = new();
// Fire and forget a task to search for the keyword
_ = Task.Run(() =>
{
try
{
// Get all names
List<string> names = GetNames();
// Perform case-insensitive search
List<string> result = names.Where(name =>
{
cts.Token.ThrowIfCancellationRequested();
return name.StartsWith(keyWord, StringComparison.OrdinalIgnoreCase);
}).ToList();
if (!result.SequenceEqual(SelectedTopicNames))
{
SelectedTopicNames = result;
}
}
catch(OperationCanceledException) { }
});
}
Основная идея состоит в том, чтобы запустить и забыть задачу, которая выполняет операцию поиска и перезаписывает свойство SelectedTopicNames, привязанное к CollectionView. Я нашел это хорошим решением, потому что когда вы переназначаете SelectedTopicNames, оно запускает событие OnPropertyChanged, которое уведомляет пользовательский интерфейс о том, что пришло время обновить отображаемые данные. Это может занять некоторое время, поскольку в худшем случае необходимо добавить кнопку 90 в пользовательский интерфейс.
Поскольку задача должна записать SelectedTopicNames, я подумал, что было бы неплохо отменить текущую задачу, чтобы избежать возможной одновременной записи и ненужного запуска события OnPropertyChanged.
Меня беспокоит то, что, согласно документации CancelAsync(), он будет только ждать процесса передачи сигнала. завершение зарегистрированных обратных вызовов, поэтому он не будет ожидать фактической отмены задачи. В этом случае, если текущая задача не может попасть на линию ThrowIfCancellationRequested между сигнализацией и повторной инициализацией источника токена отмены, то она не будет отменена и будет запущена параллельная задача.
ThrowIfCancellationRequested внутри запроса LINQ, потому что это единственное место, где я могу его эффективно использовать. Потому что GetNames() также является LINQ, и нет смысла делать его асинхронным, чтобы его можно было отменить, и SequenceEqual также не поддерживает отмену. Этот подход не гарантирует, что задача будет отменена.Я хочу добиться такого поведения, если команда вызывается до завершения текущей задачи, тогда текущая задача немедленно отменяется или, по крайней мере, не выполняет назначение SelectedTopicNames = result;. (Основная цель — не создавать событие OnPropertyChanged, поскольку это подразумевает обновление пользовательского интерфейса.)





Ваш код, скорее всего, должен выглядеть так
await cts.CancelAsync();
cts = new();
var token = cts.Token;
try{
var selectedTopicNames = await Task.Run(() =>
{
// Get all names
List<string> names = GetNames();
// Perform case-insensitive search
List<string> result = names.Where(name =>
{
token.ThrowIfCancellationRequested();
return name.StartsWith(keyWord, StringComparison.OrdinalIgnoreCase);
}).ToList();
if (!result.SequenceEqual(SelectedTopicNames))
{
return result;
}
return null;
});
token.ThrowIfCancellationRequested();
if (selectedTopicNames != null){
SelectedTopicNames = selectedTopicNames;
}
}
catch(OperationCanceledException) { }
Это устраняет несколько проблем. Прежде всего, свойство корректно обновляется в потоке пользовательского интерфейса. А поскольку токен отмены проверяется в потоке пользовательского интерфейса, установка свойства и отмена будут правильно упорядочены.
Поскольку GetNames() также является LINQ.
GetNames() возвращает List<string>, так что в этом нет ничего ленивого или LINQy, это просто простой список в памяти.
Обратите внимание, что я бы рекомендовал провести некоторые измерения производительности. Эмпирическое правило — не блокировать поток пользовательского интерфейса более чем на 50 мс, и быстрый тест показывает, что это должно равняться примерно 200 тыс. строк. Итак, сколько у вас строк и сколько времени занимает ваша операция? Действительно ли необходимы отмена и потоковая обработка?
@supersonic-developer Ничто после await Task.Run не будет выполняться до тех пор, пока ожидаемая задача не завершится, и если задача завершится с исключением, оно будет повторно выдано ожиданием. Я рекомендую измерить/оценить/профилировать код перед любой оптимизацией и попытаться найти алгоритмические улучшения или избегать выполнения ненужной работы, прежде чем бросаться в потоки, решающие проблему.
В этом случае может ли токен выдать OperationalException, даже если задача (которая предоставляет результат selectedTopicNames) еще не завершена? Я не уверен, что резьба нужна. Раньше, когда я не использовал многопоточность, добавление кнопок в пользовательский интерфейс происходило очень медленно и блокировало пользовательский интерфейс. Но в этом случае структура пользовательского интерфейса была другой, чем сейчас, например, представление коллекции было частью комбинации представления прокрутки и макета вертикального стека, и я читал об этом плохие отзывы, потому что это может испортить поддержку виртуализации.