Читай комментарии в коде, в том числе зачем там lock. Вдруг ты будешь использовать такой поиск с отменой не в UI потоке (не именно этот код, а саму логику).
Models/SearchAlgorithm.cs
using System;
using System.Threading;
using System.Threading.Tasks;
namespace EventsInModel.Models
{
public class SearchAlgorithm
{
public string CurrentFolder { get; private set; }
public event EventHandler ProgressChanged;
public async Task Search(CancellationToken cancellationToken)
{
for (int i = 0; i < 5; i++)
{
await Task.Delay(1200, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
// Можно прогресс передавать и в качестве аргумента события,
// но в данном случае, вряд ли это оправдано. Обработав событие можно получить
// доступ к отправителю события и прочитать его свойства.
CurrentFolder = i.ToString();
ProgressChanged?.Invoke(this, EventArgs.Empty);
}
}
}
}
ViewModels/MainViewModel.cs
using System;
using System.Threading;
using System.Windows.Input;
using EventsInModel.Models;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
namespace EventsInModel.ViewModels
{
// ViewModelBase из библиотеки MvvmLight
public class MainViewModel : ViewModelBase
{
//private readonly object _sync = new object();
private readonly SearchAlgorithm _search;
private string _currentFolder;
// Логику с отменой можно вынести в отдельный класс, чтобы не писать простыню
// с отменой в каждом таком месте с операцией, которая может быть отменена, а
// в UI приложениях такое сплошь и рядом.
private volatile CancellationTokenSource _lastCancellationTokenSource;
public string CurrentFolder
{
get { return _currentFolder; }
private set { Set(ref _currentFolder, value); }
}
public ICommand SearchCommand { get; }
public MainViewModel(SearchAlgorithm search)
{
_search = search;
_search.ProgressChanged += OnSearchProgressChanged;
SearchCommand = new RelayCommand(Search);
}
public override void Cleanup()
{
//lock (_sync)
{
_lastCancellationTokenSource?.Cancel();
}
_search.ProgressChanged -= OnSearchProgressChanged;
base.Cleanup();
}
/// <summary>
/// Прерывает прошлый поиск и запускает новый.
/// </summary>
private async void Search()
{
CancellationTokenSource currentTokenSource;
// В случае, если такой метод вызывать не из UI потока, то lock здесь нужен
// Если использовать только из UI потока как здесь, то lock можно удалить.
// Ещё бы я вынес логику в отдельный класс и использовал в других проектах в том числе.
//lock (_sync)
{
_lastCancellationTokenSource?.Cancel();
currentTokenSource = new CancellationTokenSource();
_lastCancellationTokenSource = currentTokenSource;
}
try
{
await _search.Search(currentTokenSource.Token);
}
catch (OperationCanceledException)
{
// Ignored.
}
finally
{
//lock (_sync)
{
currentTokenSource.Dispose();
if (ReferenceEquals(_lastCancellationTokenSource, currentTokenSource))
{
_lastCancellationTokenSource = null;
}
}
}
}
private void OnSearchProgressChanged(object sender, EventArgs e)
{
var search = (SearchAlgorithm)sender;
CurrentFolder = search.CurrentFolder;
}
}
}
Затестил я по быстрому так. Это тебе не особо нужно:
MainWindow.xaml.cs
using System.Windows;
using EventsInModel.Models;
using EventsInModel.ViewModels;
namespace EventsInModel
{
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainViewModel(new SearchAlgorithm());
DataContext = _viewModel;
Loaded += OnLoaded;
Closing += OnClosing;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
_viewModel.SearchCommand.Execute(null);
_viewModel.SearchCommand.Execute(null);
}
private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
_viewModel.Cleanup();
}
}
}
Проект с зависимостями
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MvvmLightLibsStd10" Version="5.4.1.1" />
</ItemGroup>
</Project>