Как сделать 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)

Есть идеи, как заставить это работать?