1
1
using System ;
2
+ using System . Collections . Generic ;
3
+ using System . Threading . Tasks ;
4
+ using System . Threading ;
2
5
using AsyncImageLoader . Loaders ;
3
6
using Avalonia ;
4
7
using Avalonia . Controls ;
8
+ using Avalonia . Media . Imaging ;
9
+ using System . Collections . Concurrent ;
5
10
6
11
namespace AsyncImageLoader ;
7
12
@@ -22,18 +27,50 @@ static ImageLoader()
22
27
23
28
public static IAsyncImageLoader AsyncImageLoader { get ; set ; } = new RamCachedWebImageLoader ( ) ;
24
29
25
- private static async void OnSourceChanged ( Image sender , AvaloniaPropertyChangedEventArgs args ) {
26
- var url = args . GetNewValue < string ? > ( ) ;
27
- SetIsLoading ( sender , true ) ;
30
+ private static ConcurrentDictionary < Image , CancellationTokenSource > _pendingOperations = new ConcurrentDictionary < Image , CancellationTokenSource > ( ) ;
31
+ private static async void OnSourceChanged ( Image sender , AvaloniaPropertyChangedEventArgs args ) {
32
+ var url = args . GetNewValue < string ? > ( ) ;
28
33
29
- var bitmap = url == null
30
- ? null
31
- : await AsyncImageLoader . ProvideImageAsync ( url ) ;
32
- if ( GetSource ( sender ) != url ) return ;
33
- sender . Source = bitmap ! ;
34
+ // Cancel/Add new pending operation
35
+ CancellationTokenSource ? cts = _pendingOperations . AddOrUpdate ( sender , new CancellationTokenSource ( ) ,
36
+ ( x , y ) =>
37
+ {
38
+ y . Cancel ( ) ;
39
+ return new CancellationTokenSource ( ) ;
40
+ } ) ;
34
41
35
- SetIsLoading ( sender , false ) ;
36
- }
42
+ if ( url == null )
43
+ {
44
+ ( ( ICollection < KeyValuePair < Image , CancellationTokenSource > > ) _pendingOperations ) . Remove ( new KeyValuePair < Image , CancellationTokenSource > ( sender , cts ) ) ;
45
+ sender . Source = null ;
46
+ return ;
47
+ }
48
+
49
+ SetIsLoading ( sender , true ) ;
50
+
51
+ Bitmap ? bitmap = await Task . Run ( async ( ) =>
52
+ {
53
+ try
54
+ {
55
+ // A small delay allows to cancel early if the image goes out of screen too fast (eg. scrolling)
56
+ // The Bitmap constructor is expensive and cannot be cancelled
57
+ await Task . Delay ( 10 , cts . Token ) ;
58
+
59
+ return await AsyncImageLoader . ProvideImageAsync ( url ) ;
60
+ }
61
+ catch ( TaskCanceledException )
62
+ {
63
+ return null ;
64
+ }
65
+ } ) ;
66
+
67
+ if ( bitmap != null && ! cts . Token . IsCancellationRequested )
68
+ sender . Source = bitmap ! ;
69
+
70
+ // "It is not guaranteed to be thread safe by ICollection, but ConcurrentDictionary's implementation is. Additionally, we recently exposed this API for .NET 5 as a public ConcurrentDictionary.TryRemove"
71
+ ( ( ICollection < KeyValuePair < Image , CancellationTokenSource > > ) _pendingOperations ) . Remove ( new KeyValuePair < Image , CancellationTokenSource > ( sender , cts ) ) ;
72
+ SetIsLoading ( sender , false ) ;
73
+ }
37
74
38
75
public static string ? GetSource ( Image element )
39
76
{
0 commit comments