xamarin 如何使ObservableCollection线程安全?

igetnqfo  于 2022-12-07  发布在  其他
关注(0)|答案(6)|浏览(199)
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

我正在添加/删除不在UI线程上的ObservableCollection。
我有一个名为EnqueueReport的方法要添加到集合中,还有一个DequeueReport要从集合中删除。
步骤流程如下:

  1. 1.call EnqueueReport每当请求新报告时
    1.每隔几秒钟调用一个方法来检查是否生成了报告(它有一个foreach循环,用于检查ObservableCollection中所有报告的生成状态)
    1.如果生成报告,则调用DequeueReport
    我对C#库不太了解,有人能指导我吗?
yzuktlbb

yzuktlbb1#

从.net framwork 4. 5开始,您可以使用本机集合同步。
BindingOperations.EnableCollectionSynchronization(YourCollection, YourLockObject);
YourLockObject是任何对象的执行严修,例如new Object();。每个集合使用一个。
这就消除了一些特殊类或任何东西的需要。只是启用和享受;)

[edit]*正如Mark和Ed在评论中所说的那样(感谢澄清!),这 * 并 * 不能减轻您在更新时锁定集合的负担,因为它只是同步集合-视图-绑定, 并 * 不能神奇地使集合本身成为线程安全的。[/edit]**

PS:BindingOperations位于命名空间System.Windows.Data中。

i7uq4tfw

i7uq4tfw2#

Franck在这里发布的解决方案适用于一个线程添加内容的情况,但ObservableCollection本身(以及它所基于的List)不是线程安全的。如果多个线程正在向集合写入,则可能会引入难以跟踪的bug。我编写了一个ObservableCollection版本,它使用ReaderWriteLockSlim来实现真正的线程安全。
Unfortunately, it hit the StackOverflow character limit, so here it is on PasteBin.对于多个读取器/写入器,这应该可以100%正常工作。就像常规的ObservableCollection一样,在回调中修改集合是无效的(在接收回调的线程上)。

06odsfpq

06odsfpq3#

你可以创建一个简单的线程友好版本的可观察集合。如下所示:

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);
                }
        }
    }

有了它,现在做一个大规模的查找和替换,并改变你所有的ObservableCollectionMTObservableCollection和你的好去

lg40wkob

lg40wkob4#

您可以使用ObservableConcurrentCollection类。它们位于Microsoft提供的并行扩展附加程序库中的一个包中。
您可以在Nuget上获得社区预先构建的版本:https://www.nuget.org/packages/ParallelExtensionsExtras/
或从Microsoft的以下站点获取:
https://code.msdn.microsoft.com/ParExtSamples

f5emj3cl

f5emj3cl5#

本文是为Xamarin.Forms用户编写的,但它也适用于需要使ObservableCollections线程安全的任何人:
https://codetraveler.io/2019/09/11/using-observablecollection-in-a-multi-threaded-xamarin-forms-application/
这是一个非常短的解决方案。
初始化集合后,添加以下内容:

Xamarin.Forms.BindingBase.EnableCollectionSynchronization(MyCollection, null, ObservableCollectionCallback);

并将此方法添加到同一个类中:

void ObservableCollectionCallback(IEnumerable collection, object context, Action accessMethod, bool writeAccess)
    {
        // `lock` ensures that only one thread access the collection at a time
        lock (collection)
        {
            accessMethod?.Invoke();
        }
    }

作者是布兰登·明尼克。

44u64gxh

44u64gxh6#

public class ObservableCollectionThreadSafe<T>
    : ObservableCollection<T>, IDisposable
{
    #region Data
    private Dispatcher _dispatcher;
    private ReaderWriterLockSlim _lock;
    #endregion

    #region Constructor
    public ObservableCollectionThreadSafe()
    {
        _dispatcher = Dispatcher.CurrentDispatcher;
        _lock = new ReaderWriterLockSlim();
    }
    #endregion

    #region Overrides

    /// <summary>
    /// Clear all items
    /// </summary>
    protected override void ClearItems()
    {
        _dispatcher.InvokeIfRequired(() =>
            {
                _lock.EnterWriteLock();
                try
                {
                    base.ClearItems();
                }
                finally
                {
                    _lock.ExitWriteLock();
                }
            }, DispatcherPriority.DataBind);
    }

    /// <summary>
    /// Inserts an item
    /// </summary>
    protected override void InsertItem(int index, T item)
    {
        _dispatcher.InvokeIfRequired(() =>
        {
            if (index > this.Count)
                return;

            _lock.EnterWriteLock();
            try
            {
                base.InsertItem(index, item);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }, DispatcherPriority.DataBind);

    }

    /// <summary>
    /// Moves an item
    /// </summary>
    protected override void MoveItem(int oldIndex, int newIndex)
    {
        _dispatcher.InvokeIfRequired(() =>
        {
            _lock.EnterReadLock();
            int itemCount = this.Count;
            _lock.ExitReadLock();

            if (oldIndex >= itemCount |
                newIndex >= itemCount |
                oldIndex == newIndex)
                return;

            _lock.EnterWriteLock();
            try
            {
                base.MoveItem(oldIndex, newIndex);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }, DispatcherPriority.DataBind);


    }

    /// <summary>
    /// Removes an item
    /// </summary>
    protected override void RemoveItem(int index)
    {

        _dispatcher.InvokeIfRequired(() =>
        {
            if (index >= this.Count)
                return;

            _lock.EnterWriteLock();
            try
            {
                base.RemoveItem(index);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }, DispatcherPriority.DataBind);
    }

    /// <summary>
    /// Sets an item
    /// </summary>
    protected override void SetItem(int index, T item)
    {
        _dispatcher.InvokeIfRequired(() =>
        {
            _lock.EnterWriteLock();
            try
            {
                base.SetItem(index, item);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }, DispatcherPriority.DataBind);
    }
    #endregion

    #region Public Methods
    /// <summary>
    /// Return as a cloned copy of this Collection
    /// </summary>
    public T[] ToSyncArray()
    {
        _lock.EnterReadLock();
        try
        {
            T[] _sync = new T[this.Count];
            this.CopyTo(_sync, 0);
            return _sync;
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                if (_lock != null)
                    _lock.Dispose();
            }

            // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
            // TODO: set large fields to null.

            disposedValue = true;
        }
    }

    // TODO: override a finalize only if Dispose(bool disposing) above has code to free unmanaged resources.
    // ~ObservableCollectionThreadSafe() {
    //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
    //   Dispose(false);
    // }

    // This code added to correctly implement the disposable pattern.
    public void Dispose()
    {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);
        // TODO: uncomment the following line if the finalize is overridden above.
        // GC.SuppressFinalize(this);
    }
    #endregion

    #endregion
}

/// <summary>
/// WPF Threading extension methods
/// </summary>
public static class WPFControlThreadingExtensions
{
    #region Public Methods
    /// <summary>
    /// A simple WPF threading extension method, to invoke a delegate
    /// on the correct thread if it is not currently on the correct thread
    /// Which can be used with DispatcherObject types
    /// </summary>
    /// <param name="disp">The Dispatcher object on which to do the Invoke</param>
    /// <param name="performAction">The delegate to run</param>
    /// <param name="priority">The DispatcherPriority</param>
    public static void InvokeIfRequired(this Dispatcher disp,
        Action performAction, DispatcherPriority priority)
    {
        if (disp.Thread != Thread.CurrentThread)
        {
            disp.Invoke(priority, performAction);
        }
        else
            performAction();
    }
    #endregion
}

相关问题