Skip to content

Commit 2c5c3e7

Browse files
authored
Reduce scrolling stutters (#3386)
1 parent 1f872f7 commit 2c5c3e7

File tree

3 files changed

+74
-136
lines changed

3 files changed

+74
-136
lines changed

Files/Helpers/BulkConcurrentObservableCollection.cs

Lines changed: 72 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -10,106 +10,19 @@ namespace Files.Helpers
1010
{
1111
public class BulkConcurrentObservableCollection<T> : INotifyCollectionChanged, INotifyPropertyChanged, ICollection<T>, IList<T>, ICollection, IList
1212
{
13-
private volatile bool isBulkOperationStarted;
14-
private volatile int readerCount;
15-
private volatile bool needSnapshot;
16-
private readonly SemaphoreSlim writerLock = new SemaphoreSlim(1, 1);
17-
private readonly SemaphoreSlim mutex = new SemaphoreSlim(1, 1);
18-
private readonly SemaphoreSlim snapshotLock = new SemaphoreSlim(1, 1);
13+
private bool isBulkOperationStarted;
14+
private readonly object syncRoot = new object();
1915
private readonly List<T> collection = new List<T>();
20-
private List<T> snapshot = new List<T>();
2116

22-
private void Write(Action writeFunc)
23-
{
24-
writerLock.Wait();
25-
try
26-
{
27-
writeFunc();
28-
}
29-
finally
30-
{
31-
writerLock.Release();
32-
snapshotLock.Wait();
33-
needSnapshot = true;
34-
snapshotLock.Release();
35-
}
36-
}
37-
38-
private U Write<U>(Func<U> writeFunc)
39-
{
40-
writerLock.Wait();
41-
try
42-
{
43-
return writeFunc();
44-
}
45-
finally
46-
{
47-
writerLock.Release();
48-
snapshotLock.Wait();
49-
needSnapshot = true;
50-
snapshotLock.Release();
51-
}
52-
}
53-
54-
private void Read(Action readFunc)
55-
{
56-
mutex.Wait();
57-
readerCount++;
58-
if (readerCount == 1)
59-
{
60-
writerLock.Wait();
61-
}
62-
mutex.Release();
63-
try
64-
{
65-
readFunc();
66-
}
67-
finally
68-
{
69-
mutex.Wait();
70-
readerCount--;
71-
if (readerCount == 0)
72-
{
73-
writerLock.Release();
74-
}
75-
mutex.Release();
76-
}
77-
}
78-
79-
private U Read<U>(Func<U> readFunc)
80-
{
81-
mutex.Wait();
82-
readerCount++;
83-
if (readerCount == 1)
84-
{
85-
writerLock.Wait();
86-
}
87-
mutex.Release();
88-
try
89-
{
90-
return readFunc();
91-
}
92-
finally
93-
{
94-
mutex.Wait();
95-
readerCount--;
96-
if (readerCount == 0)
97-
{
98-
writerLock.Release();
99-
}
100-
mutex.Release();
101-
}
102-
}
103-
104-
public int Count => Read(() => collection.Count);
17+
public int Count => collection.Count;
10518

10619
public bool IsReadOnly => false;
10720

10821
public bool IsFixedSize => false;
10922

110-
public bool IsSynchronized => false;
23+
public bool IsSynchronized => true;
11124

112-
public object SyncRoot => throw new NotImplementedException();
25+
public object SyncRoot => syncRoot;
11326

11427
object IList.this[int index]
11528
{
@@ -125,15 +38,11 @@ object IList.this[int index]
12538

12639
public T this[int index]
12740
{
128-
get => Read(() => collection[index]);
41+
get => collection[index];
12942
set
13043
{
131-
var item = Write(() =>
132-
{
133-
var item = collection[index];
134-
collection[index] = value;
135-
return item;
136-
});
44+
var item = collection[index];
45+
collection[index] = value;
13746
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, item));
13847
}
13948
}
@@ -163,43 +72,50 @@ public void EndBulkOperation()
16372

16473
public void Add(T item)
16574
{
166-
Write(() => collection.Add(item));
75+
lock (syncRoot)
76+
{
77+
collection.Add(item);
78+
}
79+
16780
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
16881
}
16982

17083
public void Clear()
17184
{
172-
Write(() => collection.Clear());
85+
lock (syncRoot)
86+
{
87+
collection.Clear();
88+
}
89+
17390
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
17491
}
17592

17693
public bool Contains(T item)
17794
{
178-
return Read(() => collection.Contains(item));
95+
return collection.Contains(item);
17996
}
18097

18198
public void CopyTo(T[] array, int arrayIndex)
18299
{
183-
Read(() => collection.CopyTo(array, arrayIndex));
100+
collection.CopyTo(array, arrayIndex);
184101
}
185102

186103
public bool Remove(T item)
187104
{
188-
var result = Write(() => collection.Remove(item));
105+
bool result;
106+
107+
lock (syncRoot)
108+
{
109+
result = collection.Remove(item);
110+
}
111+
189112
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
190113
return result;
191114
}
192115

193116
public IEnumerator<T> GetEnumerator()
194117
{
195-
snapshotLock.Wait();
196-
if (needSnapshot)
197-
{
198-
snapshot = Read(() => collection.ToList());
199-
needSnapshot = false;
200-
}
201-
snapshotLock.Release();
202-
return snapshot.GetEnumerator();
118+
return collection.GetEnumerator();
203119
}
204120

205121
IEnumerator IEnumerable.GetEnumerator()
@@ -209,23 +125,28 @@ IEnumerator IEnumerable.GetEnumerator()
209125

210126
public int IndexOf(T item)
211127
{
212-
return Read(() => collection.IndexOf(item));
128+
return collection.IndexOf(item);
213129
}
214130

215131
public void Insert(int index, T item)
216132
{
217-
Write(() => collection.Insert(index, item));
133+
lock (syncRoot)
134+
{
135+
collection.Insert(index, item);
136+
}
137+
218138
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
219139
}
220140

221141
public void RemoveAt(int index)
222142
{
223-
var item = Write(() =>
143+
var item = collection[index];
144+
145+
lock (syncRoot)
224146
{
225-
var item = collection[index];
226147
collection.RemoveAt(index);
227-
return item;
228-
});
148+
}
149+
229150
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
230151
}
231152

@@ -235,7 +156,12 @@ public void AddRange(IEnumerable<T> items)
235156
{
236157
return;
237158
}
238-
Write(() => collection.AddRange(items));
159+
160+
lock (syncRoot)
161+
{
162+
collection.AddRange(items);
163+
}
164+
239165
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items.ToList()));
240166
}
241167

@@ -245,50 +171,62 @@ public void InsertRange(int index, IEnumerable<T> items)
245171
{
246172
return;
247173
}
248-
Write(() => collection.InsertRange(index, items));
174+
175+
lock (syncRoot)
176+
{
177+
collection.InsertRange(index, items);
178+
}
179+
249180
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items.ToList(), index));
250181
}
251182

252183
public void RemoveRange(int index, int count)
253184
{
254-
if (count == 0)
185+
if (count <= 0)
255186
{
256187
return;
257188
}
258-
var items = Write(() =>
189+
190+
var items = collection.Skip(index).Take(count).ToList();
191+
192+
lock (syncRoot)
259193
{
260-
var items = collection.Skip(index).Take(count).ToList();
261194
collection.RemoveRange(index, count);
262-
return items;
263-
});
195+
}
196+
264197
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items));
265198
}
266199

267200
public void ReplaceRange(int index, IEnumerable<T> items)
268201
{
269202
var count = items.Count();
203+
270204
if (count == 0)
271205
{
272206
return;
273207
}
274-
var (newItems, oldItems) = Write(() =>
208+
209+
var oldItems = collection.Skip(index).Take(count).ToList();
210+
var newItems = items.ToList();
211+
212+
lock (syncRoot)
275213
{
276-
var oldItems = collection.Skip(index).Take(count).ToList();
277-
var newItems = items.ToList();
278214
collection.InsertRange(index, newItems);
279215
collection.RemoveRange(index + count, count);
280-
return (newItems, oldItems);
281-
});
216+
}
217+
282218
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems));
283219
}
284220

285221
int IList.Add(object value)
286222
{
287-
var index = Write(() =>
223+
int index;
224+
225+
lock (syncRoot)
288226
{
289-
collection.Add((T)value);
290-
return collection.Count;
291-
});
227+
index = ((IList)collection).Add((T)value);
228+
}
229+
292230
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
293231
return index;
294232
}

Files/UserControls/Selection/RectangleSelection_ListViewBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ private void RectangleSelection_PointerMoved(object sender, PointerRoutedEventAr
5858
base.DrawRectangle(currentPoint, originDragPointShifted);
5959
// Selected area considering scrolled offset
6060
var rect = new System.Drawing.Rectangle((int)Canvas.GetLeft(selectionRectangle), (int)Math.Min(originDragPoint.Y, currentPoint.Position.Y + verticalOffset), (int)selectionRectangle.Width, (int)Math.Abs(originDragPoint.Y - (currentPoint.Position.Y + verticalOffset)));
61-
foreach (var item in uiElement.Items.Except(itemsPosition.Keys))
61+
foreach (var item in uiElement.Items.ToList().Except(itemsPosition.Keys))
6262
{
6363
var listViewItem = (FrameworkElement)uiElement.ContainerFromItem(item); // Get ListViewItem
6464
if (listViewItem == null)

Files/Views/LayoutModes/GridViewBrowser.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public override void SetDragModeForItems()
129129
{
130130
if (!InstanceViewModel.IsPageTypeSearchResults)
131131
{
132-
foreach (ListedItem listedItem in FileList.Items)
132+
foreach (ListedItem listedItem in FileList.Items.ToList())
133133
{
134134
if (FileList.ContainerFromItem(listedItem) is GridViewItem gridViewItem)
135135
{

0 commit comments

Comments
 (0)