在C#中使用AutoResetEvent控制线程

时间:2015-12-09 22:39:05

标签: c# multithreading oop autoresetevent

假设我有一个A类和一个代表任务的B类。 我想进行一项实验,为了开始实验,我需要完成至少5个B任务和1个任务。

我有以下课程

abstract class Task   
{ 
    public int Id;

    public void Start(object resetEvent)
    {
        EventWaitHandle ewh = (EventWaitHandle)resetEvent;

        Thread.Sleep(new Random(DateTime.Now.Ticks.GetHashCode()).Next(5000, 14000));
        Console.WriteLine("{0} {1} starts",this.GetType().Name, Id);
        ewh.Set();
    }
}

class A : Task
{
    static int ID = 1;
    public A(EventWaitHandle resetEvent)
    {
        Id = ID++;
        new Thread(StartTask).Start(resetEvent);
    }
}

class B : Task
{
    static int ID = 1;
    public B(EventWaitHandle resetEvent)
    {
        Id = ID++;
        new Thread(StartTask).Start(resetEvent);
    }
}

以及以下主要

static void Main()
{
    A a;

    B[] bs = new B[20];
    int numberOfBs = 0;

    EventWaitHandle aResetEvent = new AutoResetEvent(false);
    EventWaitHandle bResetEvent = new AutoResetEvent(false);

    a = new A(aResetEvent);
    for (int i = 0; i < bs.Length; i++)
        bs[i] = new B(bResetEvent);

    while (numberOfBs < 5)
    {
        bResetEvent.WaitOne();
        numberOfBs++;
    }
    aResetEvent.WaitOne();

    Console.WriteLine("Experiment started with {0} B's!", numberOfBs);
    Thread.Sleep(3000); // check how many B's got in the middle
    Console.WriteLine("Experiment ended with {0} B's!", numberOfBs);
}

现在我几乎没有问题/问题:

  1. 我如何只能等待可能出现M的N个信号?

  2. 我可以只用1个AutoResetEvent来实现我想要的结果吗?

  3. 我不明白为什么所有任务都打印在一起,我希望每个任务在完成后打印,现在一切都完成。

  4. 以下代码线程是否安全?

  5. while (numberOfBs < 5)
    {
        bResetEvent.WaitOne();
        numberOfBs++;
    }
    

    可能是几个线程一起发出信号吗?如果是这样,我可以使用bResetEvent上的锁来解决这个问题吗?

2 个答案:

答案 0 :(得分:1)

  

1.我如何只能等待可能出现M的N个信号?

就像你在这里一样(有点......见#4的回答)。

  

2.我可以只用1个AutoResetEvent来实现我想要的结果吗?

是。但是在这种情况下你需要两个计数器(一个用于A类型,一个用于B类型),并且需要以线程安全的方式访问它们,例如:使用Interlocked类或使用lock语句。所有线程AB类型都将共享相同的AutoResetEvent,但会增加自己类型的计数器。一旦两个计数器达到所需值(1计数器为A5计数器为B,主线程就可以监控每个计数器并进行处理。

我建议使用lock语句方法,因为它更简单,并且允许您完全避免使用AutoResetEventlock语句使用Monitor类,它提供了与AutoResetEvent类似的功能,同时还提供了确保计数器连贯使用所需的同步。

除非你在评论中写过,否则你必须使用AutoResetEvent(为什么?),所以我猜你仍然坚持使用Interlocked(如果使用lock则没有意义你不会充分利用。)

  

3.我不明白为什么所有任务都打印在一起,我希望每个任务在完成后打印,现在一切都完成。

因为你有一个bug。您应该创建一个Random实例并使用它来确定每个任务的持续时间。您可以在创建每个任务的线程中计算持续时间,也可以同步访问(例如,使用lock)并在多个线程中使用相同的Random对象。

无法做的是为每个线程使用相同的种子值创建一个全新的Random对象,因为每个线程(或者至少是它们的大块,取决于在时间上)将最终获得完全相同的“随机”数字作为其持续时间。

你看到所有的输出一起出现,因为发生的时候:一起出现。

(是的,如果您快速连续创建多个Random个对象,无论您是明确地使用DateTime.Now还是仅让Random类,它们都将获得相同的种子用于种子的tick计数器不会经常更新,以便同时运行线程以查看不同的值。)

  

4.以下代码线程是否安全?

有问题的代码:

while (numberOfBs < 5)
{
    bResetEvent.WaitOne();
    numberOfBs++;
}

...是线程安全的,因为执行该循环的线程与任何其他线程之间共享的唯一数据是AutoResetEvent对象,并且该对象本身是线程安全的。

也就是说,对于“线程安全”的通常理解。我强烈建议你阅读Eric Lippert的文章What is this thing you call "thread safe"?询问某些东西是否是线程安全的,这是一个你可能意识到的更复杂的问题。

特别是,虽然代码以通常的方式是线程安全的(即数据保持连贯),但是您注意到在主线程可以做出反应之前,多个线程可能会到达Set()调用。到第一个。因此,您可能会错过一些通知。

答案 1 :(得分:0)

每次完成任务时,都可以通知需要a和B达到某些更改的任务。当它得到通知时,它可以检查条件是否良好,然后才进行。

输出:

Task 3 still waiting: A0, B0
B reached 1
Task 3 still waiting: A0, B1
A reached 1
Task 3 still waiting: A1, B1
B reached 2
Task 3 still waiting: A1, B2
B reached 3
Task 3 still waiting: A1, B3
A reached 2
Task 3 still waiting: A2, B3
B reached 4
Task 3 still waiting: A2, B4
B reached 5
Task 3 done: A2, B5
A reached 3
B reached 6
B reached 7
B reached 8
B reached 9
B reached 10
All done

程序:

class Program
{
    static int stageOfA = 0;
    static int stageOfB = 0;   
    private static readonly AutoResetEvent _signalStageCompleted = new AutoResetEvent(false);

    static void DoA()
    {
        for (int i = 0; i < 3; i++) {
            Thread.Sleep(100);
            Interlocked.Increment(ref stageOfA);
            Console.WriteLine($"A reached {stageOfA}");
            _signalStageCompleted.Set();
        }
    }

    static void DoB()
    {
        for (int i = 0; i < 10; i++)
        {
            Thread.Sleep(50);
            Interlocked.Increment(ref stageOfB);
            Console.WriteLine($"B reached {stageOfB}");
            _signalStageCompleted.Set();
        }
    }

    static void DoAfterB5andA1()
    {
        while( (stageOfA < 1) || (stageOfB < 5))
        {
            Console.WriteLine($"Task 3 still waiting: A{stageOfA}, B{stageOfB}");
            _signalStageCompleted.WaitOne();
        }
        Console.WriteLine($"Task 3 done: A{stageOfA}, B{stageOfB}");
    }

    static void Main(string[] args)
    {
        Task[] taskArray = { Task.Factory.StartNew(() => DoA()),
                                 Task.Factory.StartNew(() => DoB()),
                                 Task.Factory.StartNew(() => DoAfterB5andA1()) };

        Task.WaitAll(taskArray);
        Console.WriteLine("All done");
        Console.ReadLine();
    }
}