Синхронизация между потоками / атомарные проверки? - PullRequest
1 голос
/ 17 июля 2011

Мне нужно создать метод вызова, который может вызывать любой поток (например, поток 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 >> Beta.

Я могу использовать логические значения и еще много чего, но я никогда не получу настоящую атомную безопасность здесь. Я попробовал некоторые исправления, но они не сработали в случае, когда Beta >> Alpha.

Я также думал о блокировке методов Invoker.Execute и Invoker.Invoke, чтобы гарантировать, что выполнение не произойдет между постановкой в ​​очередь и ожиданием. Однако проблема заключается в том, что блокировка также поглощает WaitOne и поэтому никогда не завершается (тупик).

Как мне добиться абсолютной атомной безопасности в этой парадигме?

Примечание: я действительно работаю с этим дизайном, исходя из внешних зависимостей. Таким образом, изменение дизайна не является реальным вариантом.

EDIT : я забыл упомянуть, что хочу блокировать поведение (на основе bool block), пока делегат не будет выполнен при вызове Invoke.

Ответы [ 3 ]

1 голос
/ 17 июля 2011

Используйте Semaphore(Slim) вместо ManualResetEvent.

Создайте семафор с максимальным количеством 1, вызовите WaitOne() в вызывающем потоке и вызовите Release() в делегате.

Если вы уже позвонили Release(), WaitOne() должен немедленно вернуться.

Обязательно наберите Dispose(), когда закончите, предпочтительно в блоке using.
Если block ложно, вы не должны создавать его в первую очередь (хотя для SemaphoreSlim это не так уж и плохо).

0 голосов
/ 17 июля 2011

Я отвечаю только для того, чтобы показать описанную реализацию 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);
        }
    }
}
0 голосов
/ 17 июля 2011

Вы можете использовать мою технику :

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

Непроверенные

...