简体   繁体   English

在C#中使用AutoResetEvent控制线程

[英]Control threads using AutoResetEvent in C#

Say I have a class A and a class B representing tasks. 假设我有代表任务的A类和B类。 I want to perform an experiment, and for the experiment to start I need to finish at least 5 B tasks and only 1 A task. 我想执行一个实验,要开始实验,我需要完成至少5 B任务和仅1 A任务。

I have the following classes 我有以下课程

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

and the following main 和以下主要

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

now I have few problems/questions: 现在我有几个问题/疑问:

  1. How can I wait for only N signals out of possible M? 如何等待可能的M个信号中只有N个信号?

  2. Can I achieve the result I'm looking for with only 1 AutoResetEvent? 我可以仅使用1个AutoResetEvent来获得所需的结果吗?

  3. I don't understand why all the tasks are printed together, I expected each task to be printed when it is done and now when everything is done. 我不明白为什么所有任务都一起打印,我希望每个任务在完成时以及现在在完成所有任务时都被打印。

  4. is the following code thread safe? 以下代码线程安全吗?

.

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

could it be that couple of threads signal together? 可能是这两个线程一起发出信号? if so, can I fix that using lock on bResetEvent? 如果是这样,我可以使用bResetEvent上的锁定来修复该问题吗?

1.How can I wait for only N signals out of possible M? 1.如何等待可能的M个信号中的N个?

Just as you do here (sort of…see answer to #4). 就像您在这里所做的一样(…参见对#4的回答)。

2.Can I achieve the result I'm looking for with only 1 AutoResetEvent? 2.仅使用1个AutoResetEvent可以达到我想要的结果吗?

Yes. 是。 But you will need two counters in that case (one for the A type and one for the B type), and they will need to be accessed in a thread-safe way, eg with the Interlocked class, or using a lock statement. 但是在这种情况下,您将需要两个计数器(一个用于A类型,一个用于B类型),并且它们将需要以线程安全的方式进行访问,例如使用Interlocked类或使用lock语句。 All threads, A and B types, will share the same AutoResetEvent , but increment their own type's counter. 所有线程( AB类型)将共享相同的AutoResetEvent ,但会增加其自身类型的计数器。 The main thread can monitor each counter and process once both counters are at their desired value ( 1 for the A counter, 5 for the B counter). 主线程可以监视每个计数器和过程中一旦两个计数器处于其期望的值( 1A计数器, 5B计数器)。

I'd recommend using the lock statement approach, as it's simpler and would allow you to avoid using AutoResetEvent altogether (the lock statement uses the Monitor class, which provides some functionality similar to AutoResetEvent , while also providing the synchronization needed to ensure coherent use of the counters. 我建议您使用lock语句方法,因为它更简单,并且可以避免使用AutoResetEventlock语句使用Monitor类,该类提供与AutoResetEvent类似的功能,同时还提供同步操作以确保一致使用柜台。

Except that you've written in the comments you have to use AutoResetEvent (why?), so I guess you're stuck with Interlocked (no point in using lock if you're not going to take full advantage). 除了您已在注释中编写代码外,您还必须使用AutoResetEvent (为什么?),所以我想您会陷入Interlocked (如果您不打算充分利用lock情况,那就毫无用处了)。

3.I don't understand why all the tasks are printed together, I expected each task to be printed when it is done and now when everything is done. 3.我不明白为什么所有任务都一起打印,我希望每个任务在完成时以及现在在完成所有任务时都被打印。

Because you have a bug. 因为您有错误。 You should be creating a single Random instance and using it to determine the duration of every task. 您应该创建一个Random实例,并使用它来确定每个任务的持续时间。 You can either compute the durations in the thread that creates each task, or you can synchronize access (eg with lock ) and use the same Random object in multiple threads. 您可以在创建每个任务的线程中计算持续时间,也可以同步访问(例如,使用lock )并在多个线程中使用相同的Random对象。

What you can't do is create a whole new Random object using the same seed value for every thread, because then each thread (or at least large blocks of them, depending on timing) is going to wind up getting the exact same "random" number to use as its duration. 无法做的是为每个线程使用相同的种子值创建一个全新的Random对象,因为每个线程(或至少大块,取决于时间)最终将获得完全相同的“随机性” “用作持续时间的数字。

You see all the output coming out together, because that's when it happens : all together. 您会看到所有输出一起出现,因为那是发生的时间 :全部在一起。

(And yes, if you create multiple Random objects in quick succession, they will all get the same seed, whether you use DateTime.Now yourself explicitly, or just let the Random class do it. The tick counter used for the seed is not updated frequently enough for concurrently running threads to see different values.) (是的,如果您连续快速地创建多个Random对象,则无论您使用DateTime.Now还是自定义,还是让Random类来完成操作,它们都将获得相同的种子。用于种子的滴答计数器不会更新足以让并发运行的线程看到不同的值。)

4.is the following code thread safe? 4.以下代码线程安全吗?

The code in question: 有问题的代码:

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

…is thread safe, because the only data shared between the thread executing that loop and any other thread is the AutoResetEvent object, and that object is itself thread-safe. …是线程安全的,因为执行该循环的线程与任何其他线程之间共享的唯一数据是AutoResetEvent对象,而该对象本身也是线程安全的。

That is, for the usual understanding of "thread safe". 也就是说,对于“线程安全”的通常理解。 I highly recommend you read Eric Lippert's article What is this thing you call "thread safe"? 我强烈建议您阅读Eric Lippert的文章, 您称其为“线程安全”是什么? Asking if something is thread-safe is a much more complicated question that you probably realize. 询问某些事物是否是线程安全的,您可能会意识到这是一个复杂得多的问题。

In particular, while the code is thread-safe in the usual way (ie data remains coherent), as you note it is possible for more than one thread to reach the Set() call before the main thread can react to the first. 特别是,尽管代码以通常的方式是线程安全的(即数据保持一致),但是如您所注意到的,在主线程对第一个线程做出反应之前,可能有多个线程到达Set()调用。 Thus you may miss some notifications. 因此,您可能会错过一些通知。

The task that requires taks A and B reach certain changes could be notified each time a task is done. 每次完成任务时,都可以通知需要tak A和B完成某些更改的任务。 When it gets notified it could check if the conditions are good and proceed only then. 收到通知后,它可以检查条件是否良好,然后再进行操作。

Output: 输出:

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

Program: 程序:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM