Как сделать слегка измененный класс AutoResetEvent?

Мне нужен класс синхронизации, который ведет себя точно так же, как класс AutoResetEvent, но с одним небольшим исключением:

Вызов метода Set() должен освободить все ожидающие потоки, а не только один.

Как я могу построить такой класс? У меня просто нет идей?

  • Мартин.

5 ответов

Решение

Большое спасибо за все ваши мысли и замечания, которые я прочитал с большим интересом. Я провел еще несколько поисков здесь, на Stackru, и внезапно я нашел это, которое оказалось именно тем, что я искал. Сокращая его до двух необходимых мне методов, я получил небольшой класс:

public sealed class Signaller
{
    public void PulseAll()
    {
        lock (_lock)
        {
            Monitor.PulseAll(_lock);
        }
    }

    public bool Wait(TimeSpan maxWaitTime)
    {
        lock (_lock)
        {
            return Monitor.Wait(_lock, maxWaitTime);
        }
    }

    private readonly object _lock = new object();
}

и он делает именно то, что должен! Я поражен, что решение может быть таким простым, и я люблю такую ​​простоту. Оно прекрасно. Спасибо, Мэтью Уотсон!

  • Мартин.

Итак, у вас есть несколько потоков, выполняющих.WaitOne(), и вы хотите их освободить?

Используйте класс ManualResetEvent, и все ожидающие потоки должны освободиться...

Если на событие ссылаются все потоки в общем поле или свойстве, вы можете заменить общее поле или свойство новым не сигнализированным событием, а затем сообщить о старом. Это имеет определенную стоимость, так как вы будете регулярно создавать новые объекты синхронизации, но это будет работать. Вот пример того, как я это сделаю:

public static class Example
{
    private static volatile bool stopRunning;
    private static ReleasingAutoResetEvent myEvent;

    public static void RunExample()
    {
        using (Example.myEvent = new ReleasingAutoResetEvent())
        {
            WaitCallback work = new WaitCallback(WaitThread);

            for (int i = 0; i < 5; ++i)
            {
                ThreadPool.QueueUserWorkItem(work, i.ToString());
            }

            Thread.Sleep(500);

            for (int i = 0; i < 3; ++i)
            {
                Example.myEvent.Set();
                Thread.Sleep(5000);
            }

            Example.stopRunning = true;
            Example.myEvent.Set();
        }
    }

    private static void WaitThread(object state)
    {
        while (!Example.stopRunning)
        {
            Example.myEvent.WaitOne();
            Console.WriteLine("Thread {0} is released!", state);
        }
    }
}

public sealed class ReleasingAutoResetEvent : IDisposable
{
    private volatile ManualResetEvent manualResetEvent = new ManualResetEvent(false);

    public void Set()
    {
        ManualResetEvent eventToSet = this.manualResetEvent;
        this.manualResetEvent = new ManualResetEvent(false);
        eventToSet.Set();
        eventToSet.Dispose();
    }

    public bool WaitOne()
    {
        return this.manualResetEvent.WaitOne();
    }

    public bool WaitOne(int millisecondsTimeout)
    {
        return this.manualResetEvent.WaitOne(millisecondsTimeout);
    }

    public bool WaitOne(TimeSpan timeout)
    {
        return this.manualResetEvent.WaitOne(timeout);
    }

    public void Dispose()
    {
        this.manualResetEvent.Dispose();
    }
}

Ниже приведено еще одно более легкое решение, которое вы можете попробовать использовать класс Monitor для блокировки и разблокировки объектов. Тем не менее, я не так доволен историей очистки для этой версии ReleasingAutoResetEvent, так как Monitor может содержать ссылку на нее и поддерживать ее в течение неопределенного времени, если она не утилизирована должным образом.

Есть несколько ограничений / ошибок с этой реализацией. Во-первых, поток, который создает этот объект, будет единственным, кто сможет сигнализировать об этом с помощью вызова Set; другие потоки, которые пытаются сделать то же самое, получат исключение SynchronizationLockException. Во-вторых, поток, который его создал, никогда не сможет успешно ожидать его, поскольку он уже владеет блокировкой. Это будет эффективным решением, если у вас есть ровно один управляющий поток и несколько других ожидающих потоков.

public static class Example
{
    private static volatile bool stopRunning;
    private static ReleasingAutoResetEvent myEvent;

    public static void RunExample()
    {
        using (Example.myEvent = new ReleasingAutoResetEvent())
        {
            WaitCallback work = new WaitCallback(WaitThread);

            for (int i = 0; i < 5; ++i)
            {
                ThreadPool.QueueUserWorkItem(work, i.ToString());
            }

            Thread.Sleep(500);

            for (int i = 0; i < 3; ++i)
            {
                Example.myEvent.Set();
                Thread.Sleep(5000);
            }

            Example.stopRunning = true;
            Example.myEvent.Set();
        }
    }

    private static void WaitThread(object state)
    {
        while (!Example.stopRunning)
        {
            Example.myEvent.WaitOne();
            Console.WriteLine("Thread {0} is released!", state);
        }
    }
}

public sealed class ReleasingAutoResetEvent : IDisposable
{
    private volatile object lockObject = new object();

    public ReleasingAutoResetEvent()
    {
        Monitor.Enter(this.lockObject);
    }

    public void Set()
    {
        object objectToSignal = this.lockObject;
        object objectToLock = new object();

        Monitor.Enter(objectToLock);
        this.lockObject = objectToLock;
        Monitor.Exit(objectToSignal);
    }

    public void WaitOne()
    {
        object objectToMonitor = this.lockObject;
        Monitor.Enter(objectToMonitor);
        Monitor.Exit(objectToMonitor);
    }

    public bool WaitOne(int millisecondsTimeout)
    {
        object objectToMonitor = this.lockObject;
        bool succeeded = Monitor.TryEnter(objectToMonitor, millisecondsTimeout);

        if (succeeded)
        {
            Monitor.Exit(objectToMonitor);
        }

        return succeeded;
    }

    public bool WaitOne(TimeSpan timeout)
    {
        object objectToMonitor = this.lockObject;
        bool succeeded = Monitor.TryEnter(objectToMonitor, timeout);

        if (succeeded)
        {
            Monitor.Exit(objectToMonitor);
        }

        return succeeded;
    }

    public void Dispose()
    {
        Monitor.Exit(this.lockObject);
    }
}

Вы можете попробовать две вещи.

Используя объект Barrier, добавьте также условно добавленные потоки и сигнализируя их.

Другим может быть использование настройки подписчика издателя, как в RX. Каждый поток ожидает объекта, который он передает в коллекцию. Когда вы хотите вызвать цикл "set" поверх снимка, вызовите set для каждого члена.

Или вы можете попробовать медведей.

Другие вопросы по тегам