Как сделать ObservableCollection потокобезопасным?
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
Я добавляю/удаляю из ObservableCollection, который не находится в потоке пользовательского интерфейса.
У меня есть имя метода EnqueueReport, чтобы добавить в коллекцию и DequeueReport, чтобы удалить из коллектора.
Поток шагов выглядит следующим образом: -
- 1.call EnqueueReport всякий раз, когда запрашивается новый отчет.
- вызывать метод каждые несколько секунд, чтобы проверить, сгенерирован ли отчет (у этого есть цикл foreach, который проверяет сгенерированный статус всех отчетов в ObservableCollection).
- вызов DequeueReport, если отчет сгенерирован
Я не очень люблю библиотеки С#. Может ли кто-нибудь направить меня на это?
Ответы
Ответ 1
Вы можете создать простую нитевидную версию наблюдаемой коллекции. Как показано ниже:
public class MTObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
if (CollectionChanged != null)
foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
{
DispatcherObject dispObj = nh.Target as DispatcherObject;
if (dispObj != null)
{
Dispatcher dispatcher = dispObj.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => nh.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
nh.Invoke(this, e);
}
}
}
с этим теперь сделайте массивную находку и замените и измените все ваши ObservableCollection
на MTObservableCollection
и ваше добро пожаловать в
Ответ 2
Начиная с .net framwork 4.5 вы можете использовать встроенную синхронизацию коллекций.
BindingOperations.EnableCollectionSynchronization(YourCollection, YourLockObject);
YourLockObject
является экземпляром любого объекта, например, new Object();
, Используйте один на коллекцию.
Это исключает необходимость какого-то специального класса или чего-либо еще. Просто включите и наслаждайтесь;)
[править] Как отмечается в комментариях Марка и Эда (спасибо за разъяснения!), это не освобождает вас от блокировки коллекции при обновлении, поскольку она просто синхронизирует привязку к представлению коллекции и не делает ее магически безопасной для потока. сам. [/редактировать]
PS: BindingOperations
находится в пространстве имен System.Windows.Data
.
Ответ 3
Решение Franck, размещенное здесь, будет работать в случае, когда один поток добавляет вещи, но сам ObservableCollection (и List, на котором он основан) не являются потокобезопасными. Если в коллекцию записывается несколько потоков, могут быть введены жесткие ошибки. Я написал версию ObservableCollection, которая использует ReaderWriteLockSlim, чтобы быть поистине потокобезопасной.
К сожалению, он попал в предел символа StackOverflow, поэтому здесь он находится на PasteBin. Это должно работать на 100% с несколькими читателями/писателями. Как и обычный ObservableCollection, он недействителен для изменения коллекции в обратном вызове (в потоке, который получил обратный вызов).
Ответ 4
Вы можете использовать класс ObservableConcurrentCollection. Они находятся в пакете, предоставленном Microsoft в библиотеке Parallel Extensions Extras.
Вы можете получить его предварительно созданным сообществом на Nuget: https://www.nuget.org/packages/ParallelExtensionsExtras/
Или получите это от Microsoft здесь:
https://code.msdn.microsoft.com/ParExtSamples
Ответ 5
Я ищу способ сортировки потокобезопасной наблюдаемой коллекции без создания новой коллекции. Я попробовал решение Роберта Фрейзера из этого поста для потокобезопасной наблюдаемой коллекции, и она работает как шарм. Мне просто не хватает метода, который бы сортировал коллекцию без создания новой коллекции.
Я попытался добавить метод, как это:
public void Sort<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
{
var sortedItemsList = _collection.OrderBy(keySelector, comparer).ToList();
foreach (var item in sortedItemsList)
{
Move(IndexOf(item), sortedItemsList.IndexOf(item));
}
}
Но я получаю исключение:
System.Reflection.TargetInvocationException
HResult=0x80131604
Message=Exception has been thrown by the target of an invocation.
Source=mscorlib
StackTrace:
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) in f:\dd\ndp\clr\src\BCL\system\reflection\methodinfo.cs:line 761
at System.Delegate.DynamicInvokeImpl(Object[] args) in f:\dd\ndp\clr\src\BCL\system\delegate.cs:line 123
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) in f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs:line 954
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) in f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs:line 901
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) in f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs:line 890
at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at System.Windows.Application.Run()
at App.Main()
Inner Exception 1:
InvalidOperationException: Added item does not appear at given index '0'.
InnerException.StackTrace:
at System.Windows.Data.ListCollectionView.AdjustBefore(NotifyCollectionChangedAction action, Object item, Int32 index)
at System.Windows.Data.ListCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
Есть идеи, как заставить это работать?