跨线程同步/原子检查

本文关键字:检查 线程 同步 | 更新日期: 2023-09-27 18:02:36

我需要创建一个任何线程(例如线程B)都可以调用的方法调用器,它将在执行的特定给定点在主执行线程(head 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>> Beta,可以复制。

我可以使用布尔值之类的,但我从来没有得到真正的原子安全性。我尝试了一些修复,但是它们在Beta>> Alpha的情况下不起作用。

我还想过锁定两个调用者。执行和调用。调用方法,这样我们就可以保证执行不会发生在队列和等待之间。然而,问题是锁也包含WaitOne,因此永远不会结束(死锁)。

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

注意:这真的是一个要求,我与这个设计工作,从外部依赖。所以改变设计不是一个真正的选择。

EDIT:我忘记提到我想要一个阻塞行为(基于bool block),直到委托在调用上执行。

跨线程同步/原子检查

Semaphore(Slim)代替ManualResetEvent

创建一个最大计数为1的信号量,在调用线程中调用WaitOne(),在委托中调用Release()

如果你已经调用了Release(), WaitOne()应该立即返回。

当你完成时,确保Dispose(),最好是在using块。
如果block为false,那么首先就不应该创建它(尽管对于SemaphoreSlim来说,这并不是那么糟糕)。

你可以用我的方法:

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

未经考验的

我回答这个问题只是为了展示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);
        }
    }
}