同步机制的可观察的对象 [英] Synchronization mechanism for an observable object

查看:152
本文介绍了同步机制的可观察的对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们想象一下,我们必须同步读/写访问共享资源。多线程将访问该资源无论是在阅读和写作(最次的阅读,有时写作)。让我们假设也是每一个写操作将始终触发读操作(对象是可观测)。

Let's imagine we have to synchronize read/write access to shared resources. Multiple threads will access that resource both in read and writing (most of times for reading, sometimes for writing). Let's assume also that each write will always trigger a read operation (object is observable).

在这个例子中,我会想象一个这样的类(原谅语法和风格,这只是为了演示):

For this example I'll imagine a class like this (forgive syntax and style, it's just for illustration purposes):

class Container {
    public ObservableCollection<Operand> Operands;
    public ObservableCollection<Result> Results;
}

我倾向于使用 ReadWriterLockSlim 用于这一目的而且我把它放在容器水平(想象对象不是那么简单,一个读/写操作可能涉及到多个对象):

I'm tempted to use a ReadWriterLockSlim for this purpose moreover I'd put it at Container level (imagine object is not so simple and one read/write operation may involve multiple objects):

public ReadWriterLockSlim Lock;

操作数结果的实施,在这个例子中没有任何意义。 现在,让我们想象一些code表示遵守操作数,将产生一个结果就摆在结果

Implementation of Operand and Result has no meaning for this example. Now let's imagine some code that observes Operands and will produce a result to put in Results:

void AddNewOperand(Operand operand) {
    try {
        _container.Lock.EnterWriteLock();
        _container.Operands.Add(operand);
    }
    finally {
        _container.ExitReadLock();
    }
}

我们的hypotetical观察者会做同样的事情,但要消耗一个新的元素,它会与 EnterReadLock锁定()来获得操作数,然后 EnterWriteLock ()添加效果(让我省略code本)。这会产生,因为递归的一个例外,但如果我设置 LockRecursionPolicy.SupportsRecursion 然后我就打开我的code到死锁(从<一个href="http://msdn.microsoft.com/it-it/library/system.threading.readerwriterlockslim%28v=vs.110%29.aspx">MSDN):

Our hypotetical observer will do something similar but to consume a new element and it'll lock with EnterReadLock() to get operands and then EnterWriteLock() to add result (let me omit code for this). This will produce an exception because of recursion but if I set LockRecursionPolicy.SupportsRecursion then I'll just open my code to dead-locks (from MSDN):

在默认情况下,ReaderWriterLockSlim的新实例将与LockRecursionPolicy.NoRecursion标志创建并不允许​​递归。建议对所有新开发的这种默认的策略,因为递归引入了不必要的麻烦和让你的code更容易出现死锁

By default, new instances of ReaderWriterLockSlim are created with the LockRecursionPolicy.NoRecursion flag and do not allow recursion. This default policy is recommended for all new development, because recursion introduces unnecessary complications and makes your code more prone to deadlocks.

我要重申有关部分的净度:

I repeat relevant part for clarity:

递归[...]让你的code更容易产生死锁。

如果我没有错 LockRecursionPolicy.SupportsRecursion 如果来自同一个线程我问一个,比方说,读锁定那么的有人的其他要求对于一个写锁,然后我将有一个死锁那么什么的MSDN说是有道理的。此外递归会降低性能也以可衡量的方式(这不是我想要的,如果我使用 ReadWriterLockSlim 而不是 ReadWriterLock 显示器)。

If I'm not wrong with LockRecursionPolicy.SupportsRecursion if from same thread I ask a, let's say, read lock then someone else asks for a write lock then I'll have a dead-lock then what MSDN says makes sense. Moreover recursion will degrade performance too in a measurable way (and it's not what I want if I'm using ReadWriterLockSlim instead of ReadWriterLock or Monitor).

最后,我的问题是(请注意我不是在寻找有关通用同步机制的讨论,我想知道什么是错的此生产者/可观察/观察场景):

Finally my questions are (please note I'm not searching for a discussion about general synchronization mechanisms, I would know what's wrong for this producer/observable/observer scenario):

  • 什么是在这种情况下更好?为了避免 ReadWriterLockSlim 赞成显示器(即使在现实世界中code读取会比写更)?
  • 在放弃与这样的粗同步?这甚至可能会产生更好的性能,但它会让code复杂得多(当然不是在这个例子中,但在现实世界中)。
  • 我应该做的通知(从观察到的集合)异步?
  • 在别的东西,我看不到?
  • What's better in this situation? To avoid ReadWriterLockSlim in favor of Monitor (even if in real world code reads will be much more than writes)?
  • Give up with such coarse synchronization? This may even yield better performance but it'll make code much more complicated (of course not in this example but in real world).
  • Should I just make notifications (from observed collection) asynchronous?
  • Something else I can't see?

我知道没有一个的最佳的同步机制,使的工具的我们使用的必须是正确的,我们的情况下,但我不知道是否有一些最佳做法或我只是忽略一些线程和观察者之间非常重要的(想象一下使用微软无扩展但问题是,一般情况下,不依赖于该框架)。

I know that there is not a best synchronization mechanism so tool we use must be right one for our case but I wonder if there are some best practice or I just ignore something very important between threads and observers (imagine to use Microsoft Reactive Extensions but question is general, not tied to that framework).

我会尝试是使事件(在某种程度上)延期:

What I would try is to make events (somehow) deferred:

第一解决方案
每一个变化,将不会触发任何 Col​​lectionChanged 事件,它保存在一个队列。当供应商(即推动数据对象)已经完成,它会手动强制队列被刷新(依次提高每个事件)。这可能在另一个线程或者甚至在调用程序线程(但锁以外)来进行。

1st solution
Each change won't fire any CollectionChanged event, it's kept in a queue. When provider (object that push data) has finished it'll manually force the queue to be flushed (raising each event in sequence). This may be done in another thread or even in the caller thread (but outside the lock).

这可能会工作,但它会让一切都那么自动(每个变更通知必须由生产者本身,更多的code写的,更多的bug各地手工触发)。

It may works but it'll make everything less "automatic" (each change notification must be manually triggered by producer itself, more code to write, more bugs all around).

第2解决方案
另一种解决方案可以提供一个参考给我们的锁定应用于观察的集合。如果我换行 ReadWriterLockSlim 在自定义对象(有用的隐藏在一个易于使用的的IDisposable 对象)我可能会增加一个的ManualResetEvent 来通知所有的锁已被释放以这种方式收集本身可能(在同一个线程或其他线程再次)上升的事件。

2nd solution
Another solution may be to provide a reference to our lock to the observable collection. If I wrap ReadWriterLockSlim in a custom object (useful to hide it in a easy to use IDisposable object) I may add a ManualResetEvent to notify that all locks has been released in this way collection itself may rise events (again in the same thread or in another thread).

第三个解决方案
另一个想法可能是只让事件的异步。如果事件处理程序将需要一个锁,然后它会停下来等待它的时间框架。为此,我担心可以使用的(特别是如果从线程池)的大线量

3rd solution
Another idea could be to just make events asynchronous. If event handler will need a lock then it'll be stopped to wait it's time frame. For this I worry about the big thread amount that may be used (especially if from thread pool).

老实说,我不知道任何这些适用于现实世界中的应用(个人 - 从用户的角度来看 - 我preFER第二个,但它意味着自定义集合的一切,这让收藏意识到线程和我会避免,如果可能的话)。我不喜欢让code超过必要的复杂。

Honestly I don't know if any of these is applicable in real world application (personally - from users point of view - I prefer second one but it implies custom collection for everything and it makes collection aware of threading and I would avoid it, if possible). I wouldn't like to make code more complicated than necessary.

推荐答案

这听起来相当的多线程咸菜。这是相当具有挑战性的用递归链中,事件的这种模式工作,同时还避免死锁。您可能要考虑彻底解决该问题的设计。

This sounds like quite the multi-threading pickle. It's quite challenging to work with recursion in this chain-of-events pattern, whilst still avoiding deadlocks. You might want to consider designing around the problem entirely.

例如,你可以做加法的操作数异步的事件的认识:

For example, you could make the addition of an operand asynchronous to the raising of the event:

private readonly BlockingCollection<Operand> _additions
    = new BlockingCollection<Operand>();

public void AddNewOperand(Operand operand)
{
    _additions.Add(operand);
}

再有实际的增加发生在一个后台线程:

And then have the actual addition happen in a background thread:

private void ProcessAdditions()
{
    foreach(var operand in _additions.GetConsumingEnumerable())
    {
        _container.Lock.EnterWriteLock();
        _container.Operands.Add(operand);
        _container.Lock.ExitWriteLock();
    }
}

public void Initialize()
{
    var pump = new Thread(ProcessAdditions)
    {
        Name = "Operand Additions Pump"
    };
    pump.Start();
}

这分离牺牲了一定的连贯性 - add方法之后,code运行不会真正知道什么时候加载实际上已经发生了,也许这就是你的code的一个问题。如果是这样,这可能是重新编写订阅观察并使用工作的添加完成时发出信号:

This separation sacrifices some consistency - code running after the add method won't actually know when the add has actually happened and maybe that's a problem for your code. If so, this could be re-written to subscribe to the observation and use a Task to signal when the add completes:

public Task AddNewOperandAsync(Operand operand)
{
    var tcs = new TaskCompletionSource<byte>();

    // Compose an event handler for the completion of this task
    NotifyCollectionChangedEventHandler onChanged = null;
    onChanged = (sender, e) =>
    {
        // Is this the event for the operand we have added?
        if (e.NewItems.Contains(operand))
        {
            // Complete the task.
            tcs.SetCompleted(0);

            // Remove the event-handler.
            _container.Operands.CollectionChanged -= onChanged;
        }
    }

    // Hook in the handler.
    _container.Operands.CollectionChanged += onChanged;

    // Perform the addition.
    _additions.Add(operand);

    // Return the task to be awaited.
    return tcs.Task;
}

事件处理程序逻辑上引发后台线程抽加的消息,所以它阻止你的前台线程的可能性。如果您等待的消息泵的窗口中添加,同步方面是足够聪明的安排延续了消息泵线程上也是如此。

The event-handler logic is raised on the background thread pumping the add messages, so there is no possibility of it blocking your foreground threads. If you await the add on the message-pump for the window, the synchronization context is smart enough to schedule the continuation on the message-pump thread as well.

无论你走在工作路线与否,这一战略意味着你可以安全地从观察到的事件添加更多的操作数,而无需重新输入任何锁。

Whether you go down the Task route or not, this strategy means that you can safely add more operands from an observable event without re-entering any locks.

这篇关于同步机制的可观察的对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆