允许正好一个线程通过/不使线程不必要地等待

时间:2011-05-25 10:55:24

标签: c# multithreading silverlight

首先,让我解释一下情况......

我有一个有2个属性的类:DataA和DataB;它们是什么并不重要,重要的是每个都可以从另一个计算出来。我处于多线程环境中,并希望在必要时计算DataA / DataB(并非总是会访问这两者)。我的第一个想法是...... ...

public SomeDataObject DataA
{
    get
    {
        if (dataAisAvailable)
        {
            return dataA;
        }
        else
        {
            if (dataBisAvailable)
            {
                lock (dataACalcLock)
                {
                    // Don't want other threads recalculating dataA
                    if (dataAisAvailable)
                    {
                        return dataA;
                    }

                    ////////////////////////////////
                    // Calculate dataA from dataB //
                    ////////////////////////////////

                    dataAisAvailable = true;
                    return dataA;
                }
            }
            else
            {
                return null;
            }
        }
    }
}

使用这种模式,假设dataB确实可用,到达场景的第一个线程(让我们称之为thread1)进入并计算dataA,到目前为止一切都很好......但是,任何一直在等待的线程要计算的dataA现在必须逐个访问...不是非常有效的imho。我想让这些线程等待thread1完成计算dataA,然后所有人都为它做好准备,好像没有锁定。

我可能对ManualResetEvents有其他想法,但我不确定如何安全地确保一个且正好一个线程通过来进行计算。

我希望我已经清楚地解释过了,虽然我不相信我有。很高兴澄清......

编辑:我的不好,我在.NET 4.0上。这是在Silverlight ......

4 个答案:

答案 0 :(得分:2)

这听起来很像发明ReaderWriterLockSlim的场景(允许多个并发读取器,而一次只允许一个线程写入)。

它可能看起来像这样的东西(没有正确验证这个代码,所以如果你决定使用它,请确保它按你想要的那样工作):

private ReaderWriterLockSlim dataLock = new ReaderWriterLockSlim();

public SomeDataObject DataA
{
    get
    {
        if (dataAisAvailable)
        {
            return dataA;
        }

        dataLock.EnterReadLock();

        try
        {
            if (dataBisAvailable)
            {
                dataLock.EnterUpgradeableReadLock();

                try
                {
                    // Don't want other threads recalculating dataA
                    if (dataAisAvailable)
                    {
                        return dataA;
                    }

                    dataLock.EnterWriteLock();
                    try
                    {
                        ////////////////////////////////
                        // Calculate dataA from dataB //
                        ////////////////////////////////

                        dataAisAvailable = true;
                    }
                    finally
                    {
                        dataLock.ExitWriteLock();
                    }

                    return dataA;
                }
                finally
                {
                    dataLock.ExitUpgradeableReadLock();
                }
            }
            else
            {
                return null;
            }
        }
        finally
        {
            dataLock.EnterReadLock();
        }
    }
}

提示/插件:如果你想减少添加的try / finally结构的数量,你可以在扩展方法中包含一些(as presented in my blog),或者将它包装在IDisposable代理(as suggested by Josh Perry)中可能更干净。

答案 1 :(得分:1)

受到对我的问题的评论的启发,后来我被删除了,我检查了Lazy<>,然后选择了类似的内容......

    static SomeDataObject DefaultData;

    private Lazy<SomeDataObject> dataA = new Lazy<SomeDataObject>(() => DefaultData, LazyThreadSafetyMode.ExecutionAndPublication);
    private Lazy<SomeDataObject> dataB = new Lazy<SomeDataObject>(() => DefaultData, LazyThreadSafetyMode.ExecutionAndPublication);

    public SomeDataObject DataA
    {
        get
        {
            return dataA.Value;
        }
        set
        {
            dataA = new Lazy<SomeDataObject>(() => value, LazyThreadSafetyMode.ExecutionAndPublication);
            dataB = new Lazy<SomeDataObject>(GetDataB, LazyThreadSafetyMode.ExecutionAndPublication);
        }
    }
    public SomeDataObject DataB
    {
        get
        {
            return dataB.Value;
        }
        set
        {
            dataB = new Lazy<SomeDataObject>(() => value, LazyThreadSafetyMode.ExecutionAndPublication);
            dataA = new Lazy<SomeDataObject>(GetDataA, LazyThreadSafetyMode.ExecutionAndPublication);
        }
    }

    private SomeDataObject GetDataA()
    {
        if (DefaultData == dataB.Value)
        {
            return null;
        }

        ////////////////////////////////
        // Calculate dataA from dataB //
        // and return it.             //
        ////////////////////////////////
    }
    private SomeDataObject GetDataB()
    {
        if (DefaultData == dataA.Value)
        {
            return null;
        }

        ////////////////////////////////
        // Calculate dataA from dataB //
        // and return it.             //
        ////////////////////////////////
    }

不幸的是,在Silverlight中没有ReaderWriterSlimLock,否则看起来很有希望。一点基准测试表明,上述内容远比我自己编写的任何内容更快(更不用说做我想要的更多)了。

答案 2 :(得分:0)

建议:

if (dataAisAvailable)
        {
             //Wait for AutoResetEvent here, perhaps add a timeout and when it expires, you can return the current dataA, so threads don't wait forever. 
            return dataA;
        }
        else
        {
            if (dataBisAvailable)
            {
                lock (dataACalcLock)
                {
                    // Don't want other threads recalculating dataA
                    if (dataAisAvailable)
                    {
                        return dataA;
                    }

                    ////////////////////////////////
                    // Calculate dataA from dataB //
                    ////////////////////////////////

                    dataAisAvailable = true;
                     //Set AutoResetEvent to signalled so waiting threads can get to DataA.
                    return dataA;
                }
            }

答案 3 :(得分:0)

确定 - 您想要做的是避免重新计算A和B,同时在第一次计算后访问时避免锁定,是吗?

如果一个线程读取dataAisAvailable并发现它为true,那么就没有问题 - 线程可以使用A.如果它读取dataAisAvailable并发现它为false,那么就会出现问题并需要获取一个独占锁来确保dataAisAvailable仍为false,如果是,则计算它。我想,一个关键部分会做。如果A / B需要很长时间来计算,这将回退到内核锁定,但这只会在线程第一次发现其中一个布尔值为假时发生。

我认为你可以逃脱这一点,因为布尔人只能“单向”,从假到真,所以你可以通过一个简单的布尔检查,(我认为)。

您的'主题1'会发现dataAisAvailable为false,因此尝试获取锁定。如果成功,它会在锁内再次检查dataAisAvailable并计算或不计算。然后它退出锁,返回dataA。如果'thread 2'首先进入,在线程1检查dataAisAvailable和线程1获取锁定之间,计算dataA并退出锁定,则线程1将进入锁定,发现dataAisAvailable现在为true,因此只需使用dataA退出锁定

线程2-N将始终将dataAisAvailable视为true并获得A而不尝试获取锁。

RGDS, 马丁