Skip to content

Commit ab93f74

Browse files
authored
Implement BulkConcurrentObservableCollection (#3290)
1 parent 4f5b2c2 commit ab93f74

File tree

7 files changed

+384
-112
lines changed

7 files changed

+384
-112
lines changed

Files/BaseLayout.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -764,12 +764,12 @@ public void RightClickItemContextMenu_Opening(object sender, object e)
764764
ParentShellPageInstance.ContentPage.SelectedItemsPropertiesViewModel.CheckFileExtension();
765765
}
766766

767-
protected virtual async void Page_CharacterReceived(CoreWindow sender, CharacterReceivedEventArgs args)
767+
protected virtual void Page_CharacterReceived(CoreWindow sender, CharacterReceivedEventArgs args)
768768
{
769769
if (ParentShellPageInstance.IsCurrentInstance)
770770
{
771771
char letterPressed = Convert.ToChar(args.KeyCode);
772-
await ParentShellPageInstance.InteractionOperations.PushJumpChar(letterPressed);
772+
ParentShellPageInstance.InteractionOperations.PushJumpChar(letterPressed);
773773
}
774774
}
775775

Files/Files.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@
312312
<Compile Include="Helpers\PostBannerHelpers.cs" />
313313
<Compile Include="Helpers\RecycleBinHelpers.cs" />
314314
<Compile Include="Extensions\StringExtensions.cs" />
315-
<Compile Include="Helpers\BulkObservableCollection.cs" />
315+
<Compile Include="Helpers\BulkConcurrentObservableCollection.cs" />
316316
<Compile Include="Helpers\ThemeHelper.cs" />
317317
<Compile Include="Extensions\Win32FindDataExtensions.cs" />
318318
<Compile Include="Interacts\RemovableDevice.cs" />
@@ -1070,4 +1070,4 @@
10701070
<Target Name="AfterBuild">
10711071
</Target>
10721072
-->
1073-
</Project>
1073+
</Project>
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Collections.Specialized;
5+
using System.ComponentModel;
6+
using System.Linq;
7+
using System.Threading;
8+
9+
namespace Files.Helpers
10+
{
11+
public class BulkConcurrentObservableCollection<T> : INotifyCollectionChanged, INotifyPropertyChanged, ICollection<T>, IList<T>, ICollection, IList
12+
{
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);
19+
private readonly List<T> collection = new List<T>();
20+
private List<T> snapshot = new List<T>();
21+
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);
105+
106+
public bool IsReadOnly => false;
107+
108+
public bool IsFixedSize => false;
109+
110+
public bool IsSynchronized => false;
111+
112+
public object SyncRoot => throw new NotImplementedException();
113+
114+
object IList.this[int index]
115+
{
116+
get
117+
{
118+
return this[index];
119+
}
120+
set
121+
{
122+
this[index] = (T)value;
123+
}
124+
}
125+
126+
public T this[int index]
127+
{
128+
get => Read(() => collection[index]);
129+
set
130+
{
131+
var item = Write(() =>
132+
{
133+
var item = collection[index];
134+
collection[index] = value;
135+
return item;
136+
});
137+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, item));
138+
}
139+
}
140+
141+
public event NotifyCollectionChangedEventHandler CollectionChanged;
142+
public event PropertyChangedEventHandler PropertyChanged;
143+
144+
public void BeginBulkOperation()
145+
{
146+
isBulkOperationStarted = true;
147+
}
148+
149+
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
150+
{
151+
if (!isBulkOperationStarted)
152+
{
153+
CollectionChanged?.Invoke(this, e);
154+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
155+
}
156+
}
157+
158+
public void EndBulkOperation()
159+
{
160+
isBulkOperationStarted = false;
161+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
162+
}
163+
164+
public void Add(T item)
165+
{
166+
Write(() => collection.Add(item));
167+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
168+
}
169+
170+
public void Clear()
171+
{
172+
Write(() => collection.Clear());
173+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
174+
}
175+
176+
public bool Contains(T item)
177+
{
178+
return Read(() => collection.Contains(item));
179+
}
180+
181+
public void CopyTo(T[] array, int arrayIndex)
182+
{
183+
Read(() => collection.CopyTo(array, arrayIndex));
184+
}
185+
186+
public bool Remove(T item)
187+
{
188+
var result = Write(() => collection.Remove(item));
189+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
190+
return result;
191+
}
192+
193+
public IEnumerator<T> GetEnumerator()
194+
{
195+
snapshotLock.Wait();
196+
if (needSnapshot)
197+
{
198+
snapshot = Read(() => collection.ToList());
199+
needSnapshot = false;
200+
}
201+
snapshotLock.Release();
202+
return snapshot.GetEnumerator();
203+
}
204+
205+
IEnumerator IEnumerable.GetEnumerator()
206+
{
207+
return GetEnumerator();
208+
}
209+
210+
public int IndexOf(T item)
211+
{
212+
return Read(() => collection.IndexOf(item));
213+
}
214+
215+
public void Insert(int index, T item)
216+
{
217+
Write(() => collection.Insert(index, item));
218+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
219+
}
220+
221+
public void RemoveAt(int index)
222+
{
223+
var item = Write(() =>
224+
{
225+
var item = collection[index];
226+
collection.RemoveAt(index);
227+
return item;
228+
});
229+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
230+
}
231+
232+
public void AddRange(IEnumerable<T> items)
233+
{
234+
if (items.Count() == 0)
235+
{
236+
return;
237+
}
238+
Write(() => collection.AddRange(items));
239+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items.ToList()));
240+
}
241+
242+
public void InsertRange(int index, IEnumerable<T> items)
243+
{
244+
if (items.Count() == 0)
245+
{
246+
return;
247+
}
248+
Write(() => collection.InsertRange(index, items));
249+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items.ToList(), index));
250+
}
251+
252+
public void RemoveRange(int index, int count)
253+
{
254+
if (count == 0)
255+
{
256+
return;
257+
}
258+
var items = Write(() =>
259+
{
260+
var items = collection.Skip(index).Take(count).ToList();
261+
collection.RemoveRange(index, count);
262+
return items;
263+
});
264+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items));
265+
}
266+
267+
public void ReplaceRange(int index, IEnumerable<T> items)
268+
{
269+
var count = items.Count();
270+
if (count == 0)
271+
{
272+
return;
273+
}
274+
var (newItems, oldItems) = Write(() =>
275+
{
276+
var oldItems = collection.Skip(index).Take(count).ToList();
277+
var newItems = items.ToList();
278+
collection.InsertRange(index, newItems);
279+
collection.RemoveRange(index + count, count);
280+
return (newItems, oldItems);
281+
});
282+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems));
283+
}
284+
285+
int IList.Add(object value)
286+
{
287+
var index = Write(() =>
288+
{
289+
collection.Add((T)value);
290+
return collection.Count;
291+
});
292+
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
293+
return index;
294+
}
295+
296+
bool IList.Contains(object value) => Contains((T)value);
297+
int IList.IndexOf(object value) => IndexOf((T)value);
298+
void IList.Insert(int index, object value) => Insert(index, (T)value);
299+
void IList.Remove(object value) => Remove((T)value);
300+
void ICollection.CopyTo(Array array, int index) => CopyTo((T[])array, index);
301+
}
302+
}

Files/Helpers/BulkObservableCollection.cs

Lines changed: 0 additions & 29 deletions
This file was deleted.

Files/Interacts/Interaction.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,9 +1294,9 @@ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPrio
12941294
}
12951295
}
12961296

1297-
public Task PushJumpChar(char letter)
1297+
public void PushJumpChar(char letter)
12981298
{
1299-
return AssociatedInstance.FilesystemViewModel.SetJumpStringAsync(AssociatedInstance.FilesystemViewModel.JumpString + letter.ToString().ToLower());
1299+
AssociatedInstance.FilesystemViewModel.JumpString += letter.ToString().ToLower();
13001300
}
13011301

13021302
public async Task<string> GetHashForFileAsync(ListedItem fileItem, string nameOfAlg, CancellationToken token, Microsoft.UI.Xaml.Controls.ProgressBar progress)

0 commit comments

Comments
 (0)