控制AutoResetEvent的信号

本文关键字:信号 AutoResetEvent 控制 | 更新日期: 2023-09-27 18:26:18

我有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. 在主循环while之后,我如何继续计算名为Set的踏板?我必须在锁区内数?如果是这样,我如何向线程发送ref变量以便inc?

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

控制AutoResetEvent的信号

  1. 在主循环while之后,我如何继续计算名为Set的踏板?我必须在锁区内数?如果是这样,我如何向线程发送ref变量以便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();
    }
}

注意:

  • 上面的关键是新的Counter类。这抽象了跟踪线程进度所需的计数器,以及用于同步线程的AutoResetEvent。CCD_ 6用于递增;该事件被设置为简单地向主线程发出对至少一个计数器的改变的信号。通过这种方式,无论增加多少次或多少计数器,主线程总是能够准确地说出发生了多少次
  • 以上内容还修复了新版本代码中的一个细微错误。您已经将Random实例移出了线程方法本身,而是在A类的所有实例之间共享它。这比以前的要好,但仍然不太正确:Random类不是线程安全的,它确实依赖于内部状态来产生正确的结果。同时使用它可能导致非随机的,或者至少不正确的随机输出。在这里,我通过让主线程创建并使用唯一的Random对象来解决这个问题,在线程对象真正开始运行之前,根据需要将值传递给它们
  • 我还对代码的基本结构进行了更改。我希望这不会太令人困惑,而是提供了一些关于如何利用C#语言特性使代码更可读的见解
  • 不需要使用DateTime.Now.Ticks.GetHashCode()作为Random实例的种子。Random类已经默认使用基于系统时钟的种子
  1. 如何防止其他线程(已在运行)调用Set

如果您的第二个问题是根据上述内容解决的,这仍然需要吗?看起来可能不是。

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