【问题标题】:Control threads using AutoResetEvent in C#在 C# 中使用 AutoResetEvent 控制线程
【发布时间】:2016-11-19 09:36:38
【问题描述】:

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

我有以下课程

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. 以下代码线程安全吗?

.

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

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

【问题讨论】:

  • 也许您应该使用CountdownEvent 而不是AutoResetEvent
  • “我能达到我想要的结果吗”,你想要的结果是什么?
  • @YacoubMassad 我有义务使用这个 ResetEvent,即使我会调查这个 CountDownEvent。至于想要的结果 - 我希望能够控制 5 B 任务和 1 A 任务何时完成
  • “我希望能够控制5个B任务和1个A任务什么时候完成” --“控制”是什么方式?您可以完全“控制”它们而无需任何同步;即通过启动它们,您已经控制了它们,使得它们将“很快”完成(这就是“何时”)。大概这不是您所说的那种“控制”。你的意思是什么?请具体
  • 我的意思是准确控制它们何时完成,例如我希望能够知道至少 5 B 和 1 A 何时完成

标签: c# multithreading oop autoresetevent


【解决方案1】:

1.如何在可能的 M 个信号中只等待 N 个信号?

就像你在这里做的一样(有点……见第 4 条的答案)。

2.我能否只用 1 个 AutoResetEvent 达到我想要的结果?

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

我建议使用lock 语句方法,因为它更简单,并且可以让您完全避免使用AutoResetEventlock 语句使用Monitor 类,它提供了一些类似于@987654337 的功能@,同时还提供确保一致使用计数器所需的同步。

除了你已经写在 cmets 中你必须使用 AutoResetEvent(为什么?),所以我猜你被 Interlocked 困住了(如果你不打算使用 lock 没有意义充分利用)。

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

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

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

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

(是的,如果您快速连续创建多个 Random 对象,它们都将获得相同的种子,无论您自己明确使用 DateTime.Now,还是让 Random 类来做。滴答计数器用于种子的更新不够频繁,无法让并发运行的线程看到不同的值。)

4.下面的代码线程安全吗?

有问题的代码:

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

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

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

特别是,虽然代码以通常的方式是线程安全的(即数据保持一致),但正如您所注意到的,在主线程可以对第一的。因此您可能会错过一些通知。

【讨论】:

  • 我必须使用 AutoResetEvent,因为这是一个课堂练习,这是我被要求做的。
  • 如何获取所有通知?没有遗漏任何东西?
  • "我怎样才能得到所有的通知?" -- 如果你使用Interlocked 并让每个线程增加相对计数器,那么主线程可能会唤醒更少比设置事件的时间,您将保证当它唤醒时,它正在检查的计数器将指示已完成线程的实际数量。另一种选择是使用两个AutoResetEvent 对象,其中主线程等待一个,而任务线程等待另一个,这样一次只允许一个任务线程设置唤醒主线程的事件。
【解决方案2】:

每次任务完成时,可以通知需要任务 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();
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多