控制 AutoResetEvent 的信号

Control the signals of AutoResetEvent

我有 10 个线程 运行,我想控制信号,以便我可以知道 5 个线程何时向我发出信号,并能够根据需要阻止它们发出信号。

这是代码:

   class A
    {
        static readonly Random r = new Random(DateTime.Now.Ticks.GetHashCode());
        static int ID = 1;
        int id;

        public A()    {   id = ID++;   }

        public void SomeFunc(object o)
        {
            Thread.Sleep(r.Next(2000, 5000));
            lock (o)
            {
                Console.WriteLine("A-{0} called Set!", id);
                ((EventWaitHandle)o).Set();
            }
        }

        static void Main(string[] args)
        {
            AutoResetEvent are = new AutoResetEvent(false);
            A[] arr = new A[10];

            for (int i = 0; i < arr.Length; i++)
                arr[i] = new A();

            for (int i = 0; i < arr.Length; i++)
                new Thread(arr[i].SomeFunc).Start(are);

            int timesSetIsCalled = 0;
            while (timesSetIsCalled < 5)
            {
                are.WaitOne();
                timesSetIsCalled++;
            }

            Console.WriteLine("{0} threads called set", timesSetIsCalled);
            Thread.Sleep(7000);
            Console.WriteLine("{0} threads called set", timesSetIsCalled);
        }
    }

考虑到N个线程已经调用了Set函数 我的问题是:

  1. 如何防止其他线程(已经 运行)调用 Set?

  2. 如何在main中的while循环后继续统计调用Set的步数?我必须在锁区内计数?如果是这样,我如何将 ref 变量发送到线程以包含?

.

while (timesSetIsCalled < 5)
{
    are.WaitOne();
    timesSetIsCalled++;
}
  1. How can I continue to count the treads that called Set, after the while loop in the main? I have to count inside the lock area? if so, how can I send a ref variable to the thread in order to inc?

只有调用Set()的线程才能统计调用Set()的线程。您无法通过观察设置事件的线程之外的事件对象来判断有多少线程设置了事件。

碰巧,解决这个问题需要将计数行为移到线程中,这样做也解决了您的次要问题,即即使在您退出 while 循环后,计数仍会继续发生。

这是您的代码版本,展示了您可以如何执行此操作:

class Program
{
    static void Main(string[] args)
    {
        Random r = new Random();

        WaitCounter counter = new WaitCounter();
        A[] arr = new A[10];

        for (int i = 0; i < arr.Length; i++)
            arr[i] = new A(r.Next(2000, 5000), counter);

        for (int i = 0; i < arr.Length; i++)
            new Thread(arr[i].SomeFunc).Start();

        while (counter.Counter < 5)
        {
            counter.Wait();
        }

        Console.WriteLine("{0} threads called set", counter.Counter);
        Thread.Sleep(7000);
        Console.WriteLine("{0} threads called set", counter.Counter);
    }
}

class A
{
    private static int ID = 1;
    private static readonly Stopwatch sw = Stopwatch.StartNew();

    private readonly int id;
    private readonly int duration;
    private readonly WaitCounter counter;

    public A(int duration, WaitCounter counter)
    {
        id = ID++;
        this.duration = duration;
        this.counter = counter;
    }

    public void SomeFunc(object o)
    {
        Thread.Sleep(duration);
        counter.Increment();
        Console.WriteLine(
            @"{0}-{1} incremented counter! Elapsed time: {2:mm\:ss\.fff}",
            GetType().Name, id, sw.Elapsed);
    }
}

class WaitCounter
{
    private readonly AutoResetEvent _event = new AutoResetEvent(false);
    private int _counter;

    public int Counter { get { return _counter; } }

    public void Increment()
    {
        Interlocked.Increment(ref _counter);
        _event.Set();
    }

    public void Wait()
    {
        _event.WaitOne();
    }
}

备注:

  • 上面的关键是新的Counterclass。这抽象了跟踪线程进度所需的计数器,以及用于同步线程的 AutoResetEventInterlocked用于递增;设置事件只是为了向主线程发出信号,表明至少对一个计数器进行了更改。这样,不管多少次或递增多少计数器,主线程总是能够准确地告诉发生了多少次。
  • 以上还修复了新版本代码中的一个细微错误。您已将 Random 实例移出线程方法本身,而不是在 A class 的所有实例之间共享它。这比您以前的要好,但仍然不太正确:Random class 不是线程安全的,并且确实依赖内部状态来产生正确的结果。同时使用它会导致非随机或至少不正确的随机输出。我在这里通过让主线程创建和使用唯一的 Random 对象来解决这个问题,在线程对象实际启动之前根据需要将值传递给线程对象 运行.
  • 我还对代码的基本结构进行了更改。我希望这不会让人迷失方向,而是提供一些关于如何利用 C# 语言功能使代码更具可读性的见解。
  • 不需要使用 DateTime.Now.Ticks.GetHashCode() 作为 Random 实例的种子。 Random class 已经默认使用基于系统时钟的种子。
  1. How can I prevent other threads (already running) from calling Set?

如果您的第二个问题已根据上述内容得到解决,这是否仍然是必需的?好像不是。

但如果是,请 post 提出一个新问题来阐明您的意思。线程之间有很多不同的方式可以相互通信,从你的问题中你并不清楚你是否真的想 阻止 其他线程调用 Set() 或者如果你只是想延迟它们,在任何一种情况下,具体 条件将控制是否发生这种情况。