Thread safely ObservableCollection
This commit is contained in:
@@ -6,6 +6,9 @@ namespace Toolkit.Avalonia;
|
|||||||
public class AvaloniaDispatcher :
|
public class AvaloniaDispatcher :
|
||||||
IDispatcher
|
IDispatcher
|
||||||
{
|
{
|
||||||
|
public bool CheckAccess() =>
|
||||||
|
Dispatcher.UIThread.CheckAccess();
|
||||||
|
|
||||||
public async Task Invoke(Action action) =>
|
public async Task Invoke(Action action) =>
|
||||||
await Dispatcher.UIThread.InvokeAsync(action);
|
await Dispatcher.UIThread.InvokeAsync(action);
|
||||||
}
|
}
|
||||||
@@ -3,4 +3,6 @@
|
|||||||
public interface IDispatcher
|
public interface IDispatcher
|
||||||
{
|
{
|
||||||
Task Invoke(Action action);
|
Task Invoke(Action action);
|
||||||
|
|
||||||
|
bool CheckAccess();
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Toolkit.Foundation;
|
namespace Toolkit.Foundation;
|
||||||
|
|
||||||
public interface IScopeServiceFactory<TService>
|
public interface IServiceScopeFactory<TService>
|
||||||
{
|
{
|
||||||
(IServiceScope, TService) Create(params object?[] parameters);
|
(IServiceScope, TService) Create(params object?[] parameters);
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
using System.Reactive.Concurrency;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
|
|
||||||
namespace Toolkit.Foundation;
|
namespace Toolkit.Foundation;
|
||||||
@@ -32,6 +33,8 @@ public abstract partial class ObservableCollection<TViewModel> :
|
|||||||
{
|
{
|
||||||
private readonly System.Collections.ObjectModel.ObservableCollection<TViewModel> collection = [];
|
private readonly System.Collections.ObjectModel.ObservableCollection<TViewModel> collection = [];
|
||||||
|
|
||||||
|
private readonly Lock syncLock = new();
|
||||||
|
|
||||||
private readonly IDispatcher dispatcher;
|
private readonly IDispatcher dispatcher;
|
||||||
|
|
||||||
private readonly Dictionary<string, object> trackedProperties = [];
|
private readonly Dictionary<string, object> trackedProperties = [];
|
||||||
@@ -95,8 +98,27 @@ public abstract partial class ObservableCollection<TViewModel> :
|
|||||||
|
|
||||||
public TViewModel this[int index]
|
public TViewModel this[int index]
|
||||||
{
|
{
|
||||||
get => collection[index];
|
get
|
||||||
set => SetItem(index, value);
|
{
|
||||||
|
lock (syncLock)
|
||||||
|
{
|
||||||
|
return collection[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
lock (syncLock)
|
||||||
|
{
|
||||||
|
SetItem(index, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dispatcher.Invoke(() => SetItem(index, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object? IList.this[int index]
|
object? IList.this[int index]
|
||||||
@@ -129,11 +151,19 @@ public abstract partial class ObservableCollection<TViewModel> :
|
|||||||
|
|
||||||
public void Add(TViewModel item)
|
public void Add(TViewModel item)
|
||||||
{
|
{
|
||||||
int index = collection.Count;
|
if (dispatcher.CheckAccess())
|
||||||
InsertItem(index, item);
|
{
|
||||||
|
lock (syncLock)
|
||||||
|
{
|
||||||
|
InsertItem(collection.Count, item);
|
||||||
UpdateSelection(item);
|
UpdateSelection(item);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dispatcher.Invoke(() => Add(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Add(object item)
|
public void Add(object item)
|
||||||
{
|
{
|
||||||
@@ -263,37 +293,33 @@ public abstract partial class ObservableCollection<TViewModel> :
|
|||||||
|
|
||||||
public bool Move(int oldIndex, int newIndex)
|
public bool Move(int oldIndex, int newIndex)
|
||||||
{
|
{
|
||||||
if (oldIndex < 0)
|
if (dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
lock (syncLock)
|
||||||
|
{
|
||||||
|
if (oldIndex < 0 || newIndex < 0 || oldIndex >= Count || newIndex >= Count)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TViewModel item = this[oldIndex];
|
TViewModel item = collection[oldIndex];
|
||||||
|
collection.Move(oldIndex, newIndex);
|
||||||
|
|
||||||
bool moveSelection = false;
|
if (item is ISelectable selectable && selectable.IsSelected)
|
||||||
if (item is ISelectable oldSelection)
|
|
||||||
{
|
{
|
||||||
if (oldSelection.IsSelected)
|
|
||||||
{
|
|
||||||
moveSelection = true;
|
|
||||||
SelectedItem = default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveItem(oldIndex);
|
|
||||||
InsertItem(newIndex, item);
|
|
||||||
|
|
||||||
if (moveSelection)
|
|
||||||
{
|
|
||||||
if (item is ISelectable newSelection)
|
|
||||||
{
|
|
||||||
newSelection.IsSelected = true;
|
|
||||||
dispatcher.Invoke(() => SelectedItem = item);
|
dispatcher.Invoke(() => SelectedItem = item);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
dispatcher.Invoke(() => result = Move(oldIndex, newIndex));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool Move(int index, TViewModel item)
|
public bool Move(int index, TViewModel item)
|
||||||
{
|
{
|
||||||
@@ -367,6 +393,10 @@ public abstract partial class ObservableCollection<TViewModel> :
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool Remove(TViewModel item)
|
public bool Remove(TViewModel item)
|
||||||
|
{
|
||||||
|
if (dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
lock (syncLock)
|
||||||
{
|
{
|
||||||
int index = collection.IndexOf(item);
|
int index = collection.IndexOf(item);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
@@ -384,11 +414,19 @@ public abstract partial class ObservableCollection<TViewModel> :
|
|||||||
{
|
{
|
||||||
int newIndex = Math.Min(index, Count - 1);
|
int newIndex = Math.Min(index, Count - 1);
|
||||||
TViewModel? selectedItem = newIndex >= 0 ? this[newIndex] : default;
|
TViewModel? selectedItem = newIndex >= 0 ? this[newIndex] : default;
|
||||||
dispatcher.Invoke(() => SelectedItem = selectedItem);
|
SelectedItem = selectedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
dispatcher.Invoke(() => result = Remove(item));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void IList.Remove(object? value)
|
void IList.Remove(object? value)
|
||||||
{
|
{
|
||||||
@@ -421,8 +459,11 @@ public abstract partial class ObservableCollection<TViewModel> :
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Replace(int index,
|
public bool Replace(int index, TViewModel item)
|
||||||
TViewModel item)
|
{
|
||||||
|
if (dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
lock (syncLock)
|
||||||
{
|
{
|
||||||
if (index <= Count - 1)
|
if (index <= Count - 1)
|
||||||
{
|
{
|
||||||
@@ -436,6 +477,15 @@ public abstract partial class ObservableCollection<TViewModel> :
|
|||||||
Insert(index, item);
|
Insert(index, item);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
dispatcher.Invoke(() => result = Replace(index, item));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Revert()
|
public void Revert()
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-2
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace Toolkit.Foundation;
|
namespace Toolkit.Foundation;
|
||||||
|
|
||||||
public class ScopeServiceFactory<TService>(IServiceScopeFactory serviceScopeFactory,
|
public class ServiceScopeFactory<TService>(IServiceScopeFactory serviceScopeFactory,
|
||||||
ICache<TService, IServiceScope> cache) :
|
ICache<TService, IServiceScope> cache) :
|
||||||
IScopeServiceFactory<TService>
|
IServiceScopeFactory<TService>
|
||||||
where TService : notnull
|
where TService : notnull
|
||||||
{
|
{
|
||||||
public (IServiceScope, TService) Create(params object?[] parameters)
|
public (IServiceScope, TService) Create(params object?[] parameters)
|
||||||
@@ -6,6 +6,8 @@ namespace Toolkit.WinUI;
|
|||||||
public class WinUIDispatcher(DispatcherQueue dispatcherQueue) :
|
public class WinUIDispatcher(DispatcherQueue dispatcherQueue) :
|
||||||
IDispatcher
|
IDispatcher
|
||||||
{
|
{
|
||||||
|
public bool CheckAccess() => dispatcherQueue.HasThreadAccess;
|
||||||
|
|
||||||
public Task Invoke(Action action)
|
public Task Invoke(Action action)
|
||||||
{
|
{
|
||||||
dispatcherQueue.TryEnqueue(action.Invoke);
|
dispatcherQueue.TryEnqueue(action.Invoke);
|
||||||
|
|||||||
Reference in New Issue
Block a user