Browse Source

Task cancellation performance optimization

Ruben 10 months ago
parent
commit
7ef1bdf24e
2 changed files with 449 additions and 461 deletions
  1. 437 445
      src/PicView.Avalonia/Navigation/ImageIterator.cs
  2. 12 16
      src/PicView.Avalonia/Preloading/Preloader.cs

+ 437 - 445
src/PicView.Avalonia/Navigation/ImageIterator.cs

@@ -12,491 +12,494 @@ using PicView.Core.Gallery;
 using PicView.Core.Navigation;
 using Timer = System.Timers.Timer;
 
-namespace PicView.Avalonia.Navigation;
-
-public sealed class ImageIterator : IDisposable
+namespace PicView.Avalonia.Navigation
 {
-    #region Properties
+    public sealed class ImageIterator : IDisposable
+    {
+        #region Properties
 
-    private bool _disposed;
+        private bool _disposed;
 
-    public List<string> ImagePaths { get; private set; }
+        public List<string> ImagePaths { get; private set; }
 
-    public int CurrentIndex { get; private set; }
-    
-    public int NextIndex => GetIteration(CurrentIndex, NavigateTo.Next);
+        public int CurrentIndex { get; private set; }
 
-    public FileInfo InitialFileInfo { get; private set; } = null!;
-    public bool IsReversed { get; private set; }
-    private PreLoader PreLoader { get; } = new();
+        public int NextIndex => GetIteration(CurrentIndex, NavigateTo.Next);
 
-    private static FileSystemWatcher? _watcher;
-    public bool IsRunning { get; private set; }
-    private readonly MainViewModel? _vm;
+        public FileInfo InitialFileInfo { get; private set; } = null!;
+        public bool IsReversed { get; private set; }
+        private PreLoader PreLoader { get; } = new();
 
-    #endregion
+        private static FileSystemWatcher? _watcher;
+        public bool IsRunning { get; private set; }
+        private readonly MainViewModel? _vm;
 
-    #region Constructors
+        #endregion
 
-    public ImageIterator(FileInfo fileInfo, MainViewModel vm)
-    {
-        ArgumentNullException.ThrowIfNull(fileInfo);
-        _vm = vm;
-        ImagePaths = vm.PlatformService.GetFiles(fileInfo);
-        CurrentIndex = Directory.Exists(fileInfo.FullName) ? 0 : ImagePaths.IndexOf(fileInfo.FullName);
-        InitiateFileSystemWatcher(fileInfo);
-    }
+        #region Constructors
 
-    public ImageIterator(FileInfo fileInfo, List<string> imagePaths, int currentIndex, MainViewModel vm)
-    {
-        ArgumentNullException.ThrowIfNull(fileInfo);
-        _vm = vm;
-        ImagePaths = imagePaths;
-        CurrentIndex = currentIndex;
-        InitiateFileSystemWatcher(fileInfo);
-    }
+        public ImageIterator(FileInfo fileInfo, MainViewModel vm)
+        {
+            ArgumentNullException.ThrowIfNull(fileInfo);
+            _vm = vm;
+            ImagePaths = vm.PlatformService.GetFiles(fileInfo);
+            CurrentIndex = Directory.Exists(fileInfo.FullName) ? 0 : ImagePaths.IndexOf(fileInfo.FullName);
+            InitiateFileSystemWatcher(fileInfo);
+        }
+
+        public ImageIterator(FileInfo fileInfo, List<string> imagePaths, int currentIndex, MainViewModel vm)
+        {
+            ArgumentNullException.ThrowIfNull(fileInfo);
+            _vm = vm;
+            ImagePaths = imagePaths;
+            CurrentIndex = currentIndex;
+            InitiateFileSystemWatcher(fileInfo);
+        }
 
-    #endregion
+        #endregion
 
-    #region File Watcher
+        #region File Watcher
 
-    private void InitiateFileSystemWatcher(FileInfo fileInfo)
-    {
-        InitialFileInfo = fileInfo;
-        if (_watcher is not null)
+        private void InitiateFileSystemWatcher(FileInfo fileInfo)
         {
-            _watcher.Dispose();
-            _watcher = null;
-        }
+            InitialFileInfo = fileInfo;
+            if (_watcher is not null)
+            {
+                _watcher.Dispose();
+                _watcher = null;
+            }
 
-        _watcher = new FileSystemWatcher();
+            _watcher = new FileSystemWatcher();
 #if DEBUG
-        Debug.Assert(fileInfo.DirectoryName != null);
+            Debug.Assert(fileInfo.DirectoryName != null);
 #endif
-        _watcher.Path = fileInfo.DirectoryName;
-        _watcher.EnableRaisingEvents = true;
-        _watcher.Filter = "*.*";
-        _watcher.IncludeSubdirectories = SettingsHelper.Settings.Sorting.IncludeSubDirectories;
-        _watcher.Created += async (_, e) => await OnFileAdded(e);
-        _watcher.Deleted += async (_, e) => await OnFileDeleted(e);
-        _watcher.Renamed += async (_, e) => await OnFileRenamed(e);
-    }
-
-    private async Task OnFileAdded(FileSystemEventArgs e)
-    {
-        if (ImagePaths.Contains(e.FullPath))
-        {
-            return;
+            _watcher.Path = fileInfo.DirectoryName;
+            _watcher.EnableRaisingEvents = true;
+            _watcher.Filter = "*.*";
+            _watcher.IncludeSubdirectories = SettingsHelper.Settings.Sorting.IncludeSubDirectories;
+            _watcher.Created += async (_, e) => await OnFileAdded(e);
+            _watcher.Deleted += async (_, e) => await OnFileDeleted(e);
+            _watcher.Renamed += async (_, e) => await OnFileRenamed(e);
         }
 
-        if (e.FullPath.IsSupported() == false)
+        private async Task OnFileAdded(FileSystemEventArgs e)
         {
-            return;
-        }
+            if (ImagePaths.Contains(e.FullPath))
+            {
+                return;
+            }
 
-        var fileInfo = new FileInfo(e.FullPath);
-        if (fileInfo.Exists == false)
-        {
-            return;
-        }
+            if (e.FullPath.IsSupported() == false)
+            {
+                return;
+            }
 
-        var retries = 0;
-        while (IsRunning && retries < 10)
-        {
-            await Task.Delay(200);
-            retries++;
-        }
+            var fileInfo = new FileInfo(e.FullPath);
+            if (fileInfo.Exists == false)
+            {
+                return;
+            }
 
-        IsRunning = true;
-        
-        var sourceFileInfo = SettingsHelper.Settings.Sorting.IncludeSubDirectories ? new FileInfo(_watcher.Path) : fileInfo;
+            var retries = 0;
+            while (IsRunning && retries < 10)
+            {
+                await Task.Delay(200);
+                retries++;
+            }
 
-        var newList = await Task.FromResult(_vm.PlatformService.GetFiles(sourceFileInfo));
-        if (newList.Count == 0)
-        {
-            return;
-        }
+            IsRunning = true;
 
-        if (newList.Count == ImagePaths.Count)
-        {
-            return;
-        }
+            var sourceFileInfo = SettingsHelper.Settings.Sorting.IncludeSubDirectories
+                ? new FileInfo(_watcher.Path)
+                : fileInfo;
 
-        if (fileInfo.Exists == false)
-        {
-            return;
-        }
+            var newList = await Task.FromResult(_vm.PlatformService.GetFiles(sourceFileInfo));
+            if (newList.Count == 0)
+            {
+                return;
+            }
 
-        ImagePaths = newList;
-        
-        SetTitleHelper.RefreshTitle(_vm);
+            if (newList.Count == ImagePaths.Count)
+            {
+                return;
+            }
 
-        var index = ImagePaths.IndexOf(e.FullPath);
-        if (index < 0)
-        {
-            IsRunning = false;
-            return;
-        }
+            if (fileInfo.Exists == false)
+            {
+                return;
+            }
 
-        var nextIndex = index + 1;
-        if (index >= ImagePaths.Count)
-        {
-            nextIndex = 0;
-        }
+            ImagePaths = newList;
 
-        var prevIndex = index - 1;
-        if (prevIndex < 0)
-        {
-            prevIndex = ImagePaths.Count - 1;
-        }
+            SetTitleHelper.RefreshTitle(_vm);
 
-        var cleared = false;
-        if (PreLoader.Contains(index, ImagePaths) || PreLoader.Contains(nextIndex, ImagePaths) ||
-            PreLoader.Contains(prevIndex, ImagePaths))
-        {
-            PreLoader.RefreshAllFileInfo(ImagePaths);
-            cleared = true;
-        }
-        
-        IsRunning = false;
+            var index = ImagePaths.IndexOf(e.FullPath);
+            if (index < 0)
+            {
+                IsRunning = false;
+                return;
+            }
 
-        var isGalleryItemAdded = await GalleryFunctions.AddGalleryItem(index, fileInfo, _vm);
-        if (isGalleryItemAdded)
-        {
-            if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown && ImagePaths.Count > 1)
+            var nextIndex = index + 1;
+            if (index >= ImagePaths.Count)
+            {
+                nextIndex = 0;
+            }
+
+            var prevIndex = index - 1;
+            if (prevIndex < 0)
+            {
+                prevIndex = ImagePaths.Count - 1;
+            }
+
+            var cleared = false;
+            if (PreLoader.Contains(index, ImagePaths) || PreLoader.Contains(nextIndex, ImagePaths) ||
+                PreLoader.Contains(prevIndex, ImagePaths))
+            {
+                PreLoader.RefreshAllFileInfo(ImagePaths);
+                cleared = true;
+            }
+
+            IsRunning = false;
+
+            var isGalleryItemAdded = await GalleryFunctions.AddGalleryItem(index, fileInfo, _vm);
+            if (isGalleryItemAdded)
             {
-                if (_vm.GalleryMode is GalleryMode.BottomToClosed or GalleryMode.FullToClosed)
+                if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown && ImagePaths.Count > 1)
                 {
-                    _vm.GalleryMode = GalleryMode.ClosedToBottom;
+                    if (_vm.GalleryMode is GalleryMode.BottomToClosed or GalleryMode.FullToClosed)
+                    {
+                        _vm.GalleryMode = GalleryMode.ClosedToBottom;
+                    }
                 }
+
+                GalleryNavigation.CenterScrollToSelectedItem(_vm);
             }
 
-            GalleryNavigation.CenterScrollToSelectedItem(_vm);
+            if (cleared)
+            {
+                await Preload();
+            }
         }
 
-        if (cleared)
+        private async Task OnFileDeleted(FileSystemEventArgs e)
         {
-            await Preload();
-        }
-    }
+            if (e.FullPath.IsSupported() == false)
+            {
+                return;
+            }
 
-    private async Task OnFileDeleted(FileSystemEventArgs e)
-    {
-        if (e.FullPath.IsSupported() == false)
-        {
-            return;
-        }
+            if (ImagePaths.Contains(e.FullPath) == false)
+            {
+                return;
+            }
 
-        if (ImagePaths.Contains(e.FullPath) == false)
-        {
-            return;
-        }
-        
-        var index = ImagePaths.IndexOf(e.FullPath);
-        if (index < 0)
-        {
-            return;
-        }
-        var isSameFile = CurrentIndex == index;
-        var isCleared = false;
+            var index = ImagePaths.IndexOf(e.FullPath);
+            if (index < 0)
+            {
+                return;
+            }
 
-        if (PreLoader.Contains(index, ImagePaths))
-        {
-            await PreLoader.ClearAsync();   
-            isCleared = true;
-        }
-        
-        if (!ImagePaths.Remove(e.FullPath))
-        {
+            var isSameFile = CurrentIndex == index;
+            var isCleared = false;
+
+            if (PreLoader.Contains(index, ImagePaths))
+            {
+                await PreLoader.ClearAsync();
+                isCleared = true;
+            }
+
+            if (!ImagePaths.Remove(e.FullPath))
+            {
 #if DEBUG
-            Console.WriteLine($"Failed to remove {e.FullPath}");
+                Console.WriteLine($"Failed to remove {e.FullPath}");
 #endif
-            return;
-        }
+                return;
+            }
 
-        if (isSameFile)
-        {
-            if (ImagePaths.Count <= 0)
+            if (isSameFile)
             {
-                ErrorHandling.ShowStartUpMenu(_vm);
-                return;
+                if (ImagePaths.Count <= 0)
+                {
+                    ErrorHandling.ShowStartUpMenu(_vm);
+                    return;
+                }
+
+                await NavigationHelper.Iterate(false, _vm);
+            }
+            else
+            {
+                SetTitleHelper.SetTitle(_vm);
             }
 
-            await NavigationHelper.Iterate(false, _vm);
-        }
-        else
-        {
-            SetTitleHelper.SetTitle(_vm);
+            var removed = GalleryFunctions.RemoveGalleryItem(index, _vm);
+            if (removed)
+            {
+                if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
+                {
+                    if (ImagePaths.Count == 1)
+                    {
+                        _vm.GalleryMode = GalleryMode.BottomToClosed;
+                    }
+                }
+
+                var indexOf = ImagePaths.IndexOf(_vm.FileInfo.FullName);
+                _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug
+                CurrentIndex = indexOf;
+                GalleryNavigation.CenterScrollToSelectedItem(_vm);
+            }
+
+            FileHistoryNavigation.Remove(e.FullPath);
+            if (isCleared)
+            {
+                await Preload();
+            }
         }
 
-        var removed = GalleryFunctions.RemoveGalleryItem(index, _vm);
-        if (removed)
+        private async Task OnFileRenamed(RenamedEventArgs e)
         {
-            if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
+            if (e.FullPath.IsSupported() == false)
             {
-                if (ImagePaths.Count == 1)
+                if (ImagePaths.Contains(e.OldFullPath))
                 {
-                    _vm.GalleryMode = GalleryMode.BottomToClosed;
+                    ImagePaths.Remove(e.OldFullPath);
                 }
+
+                return;
             }
 
-            var indexOf = ImagePaths.IndexOf(_vm.FileInfo.FullName);
-            _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug
-            CurrentIndex = indexOf;
-            GalleryNavigation.CenterScrollToSelectedItem(_vm);
-        }
+            if (IsRunning)
+            {
+                return;
+            }
 
-        FileHistoryNavigation.Remove(e.FullPath);
-        if (isCleared)
-        {
-            await Preload();
-        }
-    }
+            IsRunning = true;
 
-    private async Task OnFileRenamed(RenamedEventArgs e)
-    {
-        if (e.FullPath.IsSupported() == false)
-        {
-            if (ImagePaths.Contains(e.OldFullPath))
+            var oldIndex = ImagePaths.IndexOf(e.OldFullPath);
+            var sameFile = CurrentIndex == oldIndex;
+            var fileInfo = new FileInfo(e.FullPath);
+            if (fileInfo.Exists == false)
             {
-                ImagePaths.Remove(e.OldFullPath);
+                return;
             }
 
-            return;
-        }
+            var sourceFileInfo = SettingsHelper.Settings.Sorting.IncludeSubDirectories
+                ? new FileInfo(_watcher.Path)
+                : fileInfo;
+            var newList = FileListHelper.RetrieveFiles(sourceFileInfo).ToList();
+            if (newList.Count == 0)
+            {
+                return;
+            }
 
-        if (IsRunning)
-        {
-            return;
+            if (fileInfo.Exists == false)
+            {
+                return;
+            }
+
+            ImagePaths = newList;
+
+            var index = ImagePaths.IndexOf(e.FullPath);
+            if (index < 0)
+            {
+                return;
+            }
+
+            if (fileInfo.Exists == false)
+            {
+                return;
+            }
+
+            await PreLoader.RefreshFileInfo(oldIndex, ImagePaths);
+            if (sameFile)
+            {
+                _vm.FileInfo = fileInfo;
+            }
+
+            SetTitleHelper.SetTitle(_vm);
+
+            IsRunning = false;
+            FileHistoryNavigation.Rename(e.OldFullPath, e.FullPath);
+            await Dispatcher.UIThread.InvokeAsync(() =>
+                GalleryFunctions.RenameGalleryItem(oldIndex, Path.GetFileNameWithoutExtension(e.Name), e.FullPath,
+                    _vm));
         }
 
-        IsRunning = true;
+        #endregion
+
+        #region Preloader
 
-        var oldIndex = ImagePaths.IndexOf(e.OldFullPath);
-        var sameFile = CurrentIndex == oldIndex;
-        var fileInfo = new FileInfo(e.FullPath);
-        if (fileInfo.Exists == false)
+        public void Clear()
         {
-            return;
+            PreLoader.Clear();
         }
 
-        var sourceFileInfo = SettingsHelper.Settings.Sorting.IncludeSubDirectories ? new FileInfo(_watcher.Path) : fileInfo;
-        var newList = FileListHelper.RetrieveFiles(sourceFileInfo).ToList();
-        if (newList.Count == 0)
+        public async Task Preload()
         {
-            return;
+            await PreLoader.PreLoadAsync(CurrentIndex, IsReversed, ImagePaths).ConfigureAwait(false);
         }
 
-        if (fileInfo.Exists == false)
+        public async Task AddAsync(int index, ImageModel imageModel)
         {
-            return;
+            await PreLoader.AddAsync(index, ImagePaths, imageModel).ConfigureAwait(false);
         }
 
-        ImagePaths = newList;
-
-        var index = ImagePaths.IndexOf(e.FullPath);
-        if (index < 0)
+        public PreLoadValue? GetPreLoadValue(int index)
         {
-            return;
+            return PreLoader.Get(index, ImagePaths);
         }
 
-        if (fileInfo.Exists == false)
+        public async Task<PreLoadValue?> GetPreLoadValueAsync(int index)
         {
-            return;
+            return await PreLoader.GetAsync(index, ImagePaths);
         }
 
-        await PreLoader.RefreshFileInfo(oldIndex, ImagePaths);
-        if (sameFile)
+        public PreLoadValue? GetCurrentPreLoadValue()
         {
-            _vm.FileInfo = fileInfo;
+            return PreLoader.Get(CurrentIndex, ImagePaths);
         }
 
-        SetTitleHelper.SetTitle(_vm);
-
-        IsRunning = false;
-        FileHistoryNavigation.Rename(e.OldFullPath, e.FullPath);
-        await Dispatcher.UIThread.InvokeAsync(() => GalleryFunctions.RenameGalleryItem(oldIndex, Path.GetFileNameWithoutExtension(e.Name), e.FullPath, _vm));
-    }
+        public async Task<PreLoadValue?> GetCurrentPreLoadValueAsync()
+        {
+            return await PreLoader.GetAsync(CurrentIndex, ImagePaths);
+        }
 
-    #endregion
+        public PreLoadValue? GetNextPreLoadValue()
+        {
+            var nextIndex = GetIteration(CurrentIndex, IsReversed ? NavigateTo.Previous : NavigateTo.Next);
+            return PreLoader.Get(nextIndex, ImagePaths);
+        }
 
-    #region Preloader
+        public async Task<PreLoadValue?>? GetNextPreLoadValueAsync()
+        {
+            var nextIndex = GetIteration(CurrentIndex, NavigateTo.Next);
+            return await PreLoader.GetAsync(nextIndex, ImagePaths);
+        }
 
-    public void Clear()
-    {
-        PreLoader.Clear();
-    }
+        public void RemoveItemFromPreLoader(int index)
+        {
+            PreLoader.Remove(index, ImagePaths);
+        }
 
-    public async Task Preload()
-    {
-        await PreLoader.PreLoadAsync(CurrentIndex, IsReversed, ImagePaths).ConfigureAwait(false);
-    }
+        public void RemoveCurrentItemFromPreLoader()
+        {
+            PreLoader.Remove(CurrentIndex, ImagePaths);
+        }
 
-    public async Task AddAsync(int index, ImageModel imageModel)
-    {
-        await PreLoader.AddAsync(index, ImagePaths, imageModel).ConfigureAwait(false);
-    }
+        public bool RefreshAllFileInfo()
+        {
+            return PreLoader.RefreshAllFileInfo(ImagePaths);
+        }
 
-    public PreLoadValue? GetPreLoadValue(int index)
-    {
-        return PreLoader.Get(index, ImagePaths);
-    }
+        #endregion
 
-    public async Task<PreLoadValue?> GetPreLoadValueAsync(int index)
-    {
-        return await PreLoader.GetAsync(index, ImagePaths);
-    }
+        #region Navigation
 
-    public PreLoadValue? GetCurrentPreLoadValue()
-    {
-        return PreLoader.Get(CurrentIndex, ImagePaths);
-    }
+        public async Task ReloadFileList()
+        {
+            ImagePaths = await Task.FromResult(_vm.PlatformService.GetFiles(InitialFileInfo)).ConfigureAwait(false);
+            CurrentIndex = ImagePaths.IndexOf(_vm.FileInfo.FullName);
 
-    public async Task<PreLoadValue?> GetCurrentPreLoadValueAsync()
-    {
-        return await PreLoader.GetAsync(CurrentIndex, ImagePaths);
-    }
+            InitiateFileSystemWatcher(InitialFileInfo);
+        }
 
-    public PreLoadValue? GetNextPreLoadValue()
-    {
-        var nextIndex = GetIteration(CurrentIndex, IsReversed ? NavigateTo.Previous : NavigateTo.Next);
-        return PreLoader.Get(nextIndex, ImagePaths);
-    }
+        public async Task QuickReload()
+        {
+            RemoveCurrentItemFromPreLoader();
+            await IterateToIndex(CurrentIndex, new CancellationTokenSource()).ConfigureAwait(false);
+        }
 
-    public async Task<PreLoadValue?>? GetNextPreLoadValueAsync()
-    {
-        var nextIndex = GetIteration(CurrentIndex, NavigateTo.Next);
-        return await PreLoader.GetAsync(nextIndex, ImagePaths);
-    }
+        public int GetIteration(int index, NavigateTo navigateTo, bool skip1 = false, bool skip10 = false,
+            bool skip100 = false)
+        {
+            int next;
 
-    public void RemoveItemFromPreLoader(int index)
-    {
-        PreLoader.Remove(index, ImagePaths);
-    }
+            if (skip100)
+            {
+                if (ImagePaths.Count > PreLoader.MaxCount)
+                {
+                    PreLoader.Clear();
+                }
+            }
 
-    public void RemoveCurrentItemFromPreLoader()
-    {
-        PreLoader.Remove(CurrentIndex, ImagePaths);
-    }
+            // Determine skipAmount based on input flags
+            var skipAmount = skip100 ? 100 : skip10 ? 10 : skip1 ? 2 : 1;
 
-    public bool RefreshAllFileInfo()
-    {
-        return PreLoader.RefreshAllFileInfo(ImagePaths);
-    }
+            switch (navigateTo)
+            {
+                case NavigateTo.Next:
+                case NavigateTo.Previous:
+                    var indexChange = navigateTo == NavigateTo.Next ? skipAmount : -skipAmount;
+                    IsReversed = navigateTo == NavigateTo.Previous;
 
-    #endregion
+                    if (SettingsHelper.Settings.UIProperties.Looping)
+                    {
+                        // Calculate new index with looping
+                        next = (index + indexChange + ImagePaths.Count) % ImagePaths.Count;
+                    }
+                    else
+                    {
+                        // Calculate new index without looping and ensure bounds
+                        var newIndex = index + indexChange;
+                        if (newIndex < 0)
+                        {
+                            return 0;
+                        }
 
-    #region Navigation
+                        if (newIndex >= ImagePaths.Count)
+                        {
+                            return ImagePaths.Count - 1;
+                        }
 
-    public async Task ReloadFileList()
-    {
-        ImagePaths = await Task.FromResult(_vm.PlatformService.GetFiles(InitialFileInfo)).ConfigureAwait(false);
-        CurrentIndex = ImagePaths.IndexOf(_vm.FileInfo.FullName);
+                        next = newIndex;
+                    }
 
-        InitiateFileSystemWatcher(InitialFileInfo);
-    }
+                    break;
 
-    public async Task QuickReload()
-    {
-        RemoveCurrentItemFromPreLoader();
-        await IterateToIndex(CurrentIndex, new CancellationTokenSource()).ConfigureAwait(false);
-    }
+                case NavigateTo.First:
+                case NavigateTo.Last:
+                    if (ImagePaths.Count > PreLoader.MaxCount)
+                    {
+                        PreLoader.Clear();
+                    }
 
-    public int GetIteration(int index, NavigateTo navigateTo, bool skip1 = false, bool skip10 = false, bool skip100 = false)
-    {
-        int next;
+                    next = navigateTo == NavigateTo.First ? 0 : ImagePaths.Count - 1;
+                    break;
 
-        if (skip100)
-        {
-            if (ImagePaths.Count > PreLoader.MaxCount)
-            {
-                PreLoader.Clear();
+                default:
+                    return -1;
             }
-        }
-    
-        // Determine skipAmount based on input flags
-        var skipAmount = skip100 ? 100 : skip10 ? 10 : skip1 ? 2 : 1;
-
-        switch (navigateTo)
-        {
-            case NavigateTo.Next:
-            case NavigateTo.Previous:
-                var indexChange = navigateTo == NavigateTo.Next ? skipAmount : -skipAmount;
-                IsReversed = navigateTo == NavigateTo.Previous;
 
-                if (SettingsHelper.Settings.UIProperties.Looping)
-                {
-                    // Calculate new index with looping
-                    next = (index + indexChange + ImagePaths.Count) % ImagePaths.Count;
-                }
-                else
-                {
-                    // Calculate new index without looping and ensure bounds
-                    var newIndex = index + indexChange;
-                    if (newIndex < 0) return 0;
-                    if (newIndex >= ImagePaths.Count) return ImagePaths.Count - 1;
-
-                    next = newIndex;
-                }
-                break;
-
-            case NavigateTo.First:
-            case NavigateTo.Last:
-                if (ImagePaths.Count > PreLoader.MaxCount)
-                {
-                    PreLoader.Clear();
-                }
-                next = navigateTo == NavigateTo.First ? 0 : ImagePaths.Count - 1;
-                break;
-
-            default:
-                return -1;
+            return next;
         }
 
-        return next;
-    }
-
-    public async Task NextIteration(NavigateTo navigateTo, CancellationTokenSource cts)
-    {
-        var index = GetIteration(CurrentIndex, navigateTo, SettingsHelper.Settings.ImageScaling.ShowImageSideBySide);
-        if (index < 0)
-        {
-            return;
-        }
-
-        if (!MainKeyboardShortcuts.IsKeyHeldDown)
-        {
-            await IterateToIndex(index, cts).ConfigureAwait(false);
-        }
-        else
+        public async Task NextIteration(NavigateTo navigateTo, CancellationTokenSource cts)
         {
-            await TimerIteration(index, cts).ConfigureAwait(false);
-        }
-    }
+            var index = GetIteration(CurrentIndex, navigateTo,
+                SettingsHelper.Settings.ImageScaling.ShowImageSideBySide);
+            if (index < 0)
+            {
+                return;
+            }
 
-    public async Task IterateToIndex(int index, CancellationTokenSource cts)
-    {
-        if (index < 0 || index >= ImagePaths.Count)
-        {
-            ErrorHandling.ShowStartUpMenu(_vm);
-            return;
-        }
-        
-        // UI is more responsive when started in new thread
-        var isMainThread = Dispatcher.UIThread.CheckAccess();
-        if (isMainThread)
-        {
-            await Task.Run(async () => { await Iterate(); }, cts.Token).ConfigureAwait(false);
-        }
-        else
-        {
-            await Iterate().ConfigureAwait(false);
+            if (!MainKeyboardShortcuts.IsKeyHeldDown)
+            {
+                await IterateToIndex(index, cts).ConfigureAwait(false);
+            }
+            else
+            {
+                await TimerIteration(index, cts).ConfigureAwait(false);
+            }
         }
 
-        return;
-
-        async Task Iterate()
+        public async Task IterateToIndex(int index, CancellationTokenSource cts)
         {
+            if (index < 0 || index >= ImagePaths.Count)
+            {
+                ErrorHandling.ShowStartUpMenu(_vm);
+                return;
+            }
+            
             try
             {
                 CurrentIndex = index;
@@ -507,7 +510,7 @@ public sealed class ImageIterator : IDisposable
                 {
                     if (preloadValue.IsLoading)
                     {
-                        TryShowPreview(preloadValue);
+                        TryShowPreview();
                     }
 
                     while (preloadValue.IsLoading)
@@ -523,7 +526,7 @@ public sealed class ImageIterator : IDisposable
                 }
                 else
                 {
-                    TryShowPreview(preloadValue);
+                    TryShowPreview();
                     preloadValue = await PreLoader.GetAsync(CurrentIndex, ImagePaths).ConfigureAwait(false);
                 }
 
@@ -549,15 +552,20 @@ public sealed class ImageIterator : IDisposable
                     {
                         _vm.SecondaryImageSource = nextPreloadValue.ImageModel?.Image;
                     }
-                    cts.Token.ThrowIfCancellationRequested();
-                    await UpdateImage.UpdateSource(_vm, index, ImagePaths, IsReversed, preloadValue, nextPreloadValue)
-                        .ConfigureAwait(false);
+
+                    if (!cts.IsCancellationRequested)
+                    {
+                        await UpdateImage.UpdateSource(_vm, index, ImagePaths, IsReversed, preloadValue, nextPreloadValue)
+                            .ConfigureAwait(false);
+                    }
                 }
                 else
                 {
-                    cts.Token.ThrowIfCancellationRequested();
-                    await UpdateImage.UpdateSource(_vm, index, ImagePaths, IsReversed, preloadValue)
-                        .ConfigureAwait(false);
+                    if (!cts.IsCancellationRequested)
+                    {
+                        await UpdateImage.UpdateSource(_vm, index, ImagePaths, IsReversed, preloadValue)
+                            .ConfigureAwait(false);
+                    }
                 }
 
                 if (ImagePaths.Count > 1)
@@ -603,97 +611,81 @@ public sealed class ImageIterator : IDisposable
 
             return;
 
-            void TryShowPreview(PreLoadValue preloadValue)
+            void TryShowPreview()
             {
                 SetTitleHelper.SetLoadingTitle(_vm);
-                
-                if (preloadValue is null)
-                {
-                    return;
-                }
-
-                if (!preloadValue.IsLoading)
-                {
-                    return;
-                }
-
-                if (index != CurrentIndex)
-                {
-                    return;
-                }
-
                 _vm.IsLoading = true;
                 _vm.ImageSource = null;
                 _vm.SecondaryImageSource = null;
             }
         }
-    }
 
-    private static Timer? _timer;
+        private static Timer? _timer;
 
-    internal async Task TimerIteration(int index, CancellationTokenSource cts)
-    {
-        if (_timer is null)
+        private async Task TimerIteration(int index, CancellationTokenSource cts)
         {
-            _timer = new Timer
+            if (_timer is null)
             {
-                AutoReset = false,
-                Enabled = true
-            };
-        }
-        else if (_timer.Enabled)
-        {
-            if (!MainKeyboardShortcuts.IsKeyHeldDown)
+                _timer = new Timer
+                {
+                    AutoReset = false,
+                    Enabled = true
+                };
+            }
+            else if (_timer.Enabled)
             {
-                _timer = null;
+                if (!MainKeyboardShortcuts.IsKeyHeldDown)
+                {
+                    _timer = null;
+                }
+
+                return;
             }
 
-            return;
+            _timer.Interval = TimeSpan.FromSeconds(SettingsHelper.Settings.UIProperties.NavSpeed).TotalMilliseconds;
+            _timer.Start();
+            await IterateToIndex(index, cts).ConfigureAwait(false);
         }
 
-        _timer.Interval = TimeSpan.FromSeconds(SettingsHelper.Settings.UIProperties.NavSpeed).TotalMilliseconds;
-        _timer.Start();
-        await IterateToIndex(index, cts).ConfigureAwait(false);
-    }
-
-    public void UpdateFileListAndIndex(List<string> fileList, int index)
-    {
-        ImagePaths = fileList;
-        CurrentIndex = index;
-    }
+        public void UpdateFileListAndIndex(List<string> fileList, int index)
+        {
+            ImagePaths = fileList;
+            CurrentIndex = index;
+        }
 
-    #endregion
+        #endregion
 
-    #region IDisposable
+        #region IDisposable
 
-    public void Dispose()
-    {
-        Dispose(true);
-        GC.SuppressFinalize(this);
-    }
-
-    private void Dispose(bool disposing)
-    {
-        if (_disposed)
+        public void Dispose()
         {
-            return;
+            Dispose(true);
+            GC.SuppressFinalize(this);
         }
 
-        if (disposing)
+        private void Dispose(bool disposing)
         {
-            _watcher?.Dispose();
-            Clear();
-            _timer?.Dispose();
-            PreLoader.Dispose();
+            if (_disposed)
+            {
+                return;
+            }
+
+            if (disposing)
+            {
+                _watcher?.Dispose();
+                Clear();
+                _timer?.Dispose();
+                PreLoader.Dispose();
+            }
+
+            _disposed = true;
         }
 
-        _disposed = true;
-    }
+        ~ImageIterator()
+        {
+            Dispose(false);
+        }
 
-    ~ImageIterator()
-    {
-        Dispose(false);
+        #endregion
     }
-
-    #endregion
 }

+ 12 - 16
src/PicView.Avalonia/Preloading/Preloader.cs

@@ -393,12 +393,21 @@ namespace PicView.Avalonia.Preloading
         /// <param name="list">The list of image paths.</param>
         public async Task PreLoadAsync(int currentIndex, bool reverse, List<string> list)
         {
-            if (_cancellationTokenSource is null)
+            if (list == null)
             {
-                _cancellationTokenSource = new CancellationTokenSource();
-                _cancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(5));
+#if DEBUG
+                Trace.WriteLine($"{nameof(PreLoader)}.{nameof(PreLoadAsync)} list null \n{currentIndex}");
+#endif
+                return;
             }
 
+            if (IsRunning)
+            {
+                return;
+            }
+            
+            _cancellationTokenSource ??= new CancellationTokenSource();
+
             try
             {
                 await PreLoadInternalAsync(currentIndex, reverse, list, _cancellationTokenSource.Token);
@@ -418,19 +427,6 @@ namespace PicView.Avalonia.Preloading
         private async Task PreLoadInternalAsync(int currentIndex, bool reverse, List<string> list,
             CancellationToken token)
         {
-            if (list == null)
-            {
-#if DEBUG
-                Trace.WriteLine($"{nameof(PreLoader)}.{nameof(PreLoadInternalAsync)} list null \n{currentIndex}");
-#endif
-                return;
-            }
-
-            if (IsRunning)
-            {
-                return;
-            }
-
             IsRunning = true;
 
             var count = list.Count;