跨线程/原子检查同步?

时间:2011-07-17 03:39:26

标签: c# .net multithreading synchronization atomicity

我需要创建一个方法调用程序,任何线程(例如,线程B)可以调用,它将在执行中的特定给定点的主执行线程(Thead A)上执行。

示例用法如下:

static Invoker Invoker = new Invoker();

static void ThreadA()
{
    new Thread(ThreadB).Start();

    Thread.Sleep(...); // Hypothetic Alpha

    Invoker.Invoke(delegate { Console.WriteLine("Action"); }, true);

    Console.WriteLine("Done");

    Console.ReadLine();
}

static void ThreadB()
{
    Thread.Sleep(...); // Hypothetic Beta

    Invoker.Execute();
}

Invoker类看起来像这样:

public class Invoker
{
    private Queue<Action> Actions { get; set; }

    public Invoker()
    {
        this.Actions = new Queue<Action>();
    }

    public void Execute()
    {
        while (this.Actions.Count > 0)
        {
            this.Actions.Dequeue()();
        }
    }

    public void Invoke(Action action, bool block = true)
    {
        ManualResetEvent done = new ManualResetEvent(!block);

        this.Actions.Enqueue(delegate
        {
            action();
            if (block) done.Set();
        });

        if (block)
        {
            done.WaitOne();
        }
    }
}

这在大多数情况下都可以正常工作,但如果由于任何原因执行(因此Set)在WaitOne之前完成,则不会这样做,在这种情况下它只会冻结(它允许线程继续,然后阻止)。如果Alpha&gt;&gt;那可以再现。贝塔。

我可以使用布尔值等等,但我在这里永远不会得到真正的原子安全。我尝试了一些修复,但是在Beta&gt;&gt;的情况下它们不起作用。阿尔法。

我还想过锁定Invoker.Execute和Invoker.Invoke方法,这样我们就可以保证在enqueing和waiting之间不会执行。但是,问题是锁也会使WaitOne失败,因此永远不会完成(死锁)。

我应该如何在这个范例中获得绝对的原子安全?

注意:我需要从外部依赖项开始使用此设计。因此,改变设计不是一个真正的选择。

编辑:我确实忘了提到我想要一个阻止行为(基于bool block),直到在Invoke调用上执行委托。

3 个答案:

答案 0 :(得分:1)

使用Semaphore(Slim)代替ManualResetEvent

创建一个最大数量为1的信号量,在调用线程中调用WaitOne(),然后在代理中调用Release()

如果您已拨打Release()WaitOne()应立即返回。

完成后请务必Dispose(),最好是using块 如果block为false,则不应该首先创建它(尽管对于SemaphoreSlim,这并不是那么糟糕。)

答案 1 :(得分:0)

您可以使用my technique

public void BlockingInvoke(Action action)
{
    volatile bool isCompleted = false;
    volatile bool isWaiting = false;
    ManualResetEventSlim waiter = new ManualResetEventSlim();

    this.Actions.Enqueue(delegate
    {
        action();

        isCompleted = true;
        Thread.MemoryBarrier();
        if (!isWaiting) 
            waiter.Dispose();
        else
            waiter.Set();
    });

    isWaiting = true;
    Thread.MemoryBarrier();
    if (!isCompleted)
        waiter.Wait();
    waiter.Dispose();
}

未测试

答案 2 :(得分:0)

我的回答只是为了展示SLaks所描述的实施以及我的解决方案,以确保使用锁进行适当和独特的处理。它对改进和批评是开放的,但它确实有效。

public class Invoker
{
    private Queue<Action> Actions { get; set; }

    public Invoker()
    {
        this.Actions = new Queue<Action>();
    }

    public void Execute()
    {
        while (this.Actions.Count > 0)
        {
            this.Actions.Dequeue()();
        }
    }

    public void Invoke(Action action, bool block = true)
    {
        if (block)
        {
            SemaphoreSlim semaphore = new SemaphoreSlim(1);
            bool disposed = false;

            this.Actions.Enqueue(delegate
            {
                action();
                semaphore.Release();

                lock (semaphore)
                {
                    semaphore.Dispose();
                    disposed = true;
                }
            });

            lock (semaphore)
            {
                if (!disposed)
                {
                    semaphore.Wait();
                    semaphore.Dispose();
                }
            }
        }
        else
        {
            this.Actions.Enqueue(action);
        }
    }
}