使用C#中的AutoResetEvent控制线程
本文关键字:控制线 线程 控制 AutoResetEvent 中的 使用 | 更新日期: 2023-09-27 18:26:19
假设我有一个类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);
}
现在我有几个问题:
如何在可能的M个信号中只等待N个信号?
我只需要1个AutoResetEvent就能达到我想要的结果吗?
我不明白为什么所有的任务都打印在一起,我希望每个任务都能在完成时打印出来,现在一切都完成了。
以下代码线程安全吗?
while (numberOfBs < 5)
{
bResetEvent.WaitOne();
numberOfBs++;
}
可能是这对线程一起发出信号吗?如果是,我可以使用bResetEvent上的锁定来解决这个问题吗?
1.如何在可能的M个信号中只等待N个信号?
正如您在这里所做的(有点像…请参阅#4的答案)。
2.我只需要1个AutoResetEvent就能达到我想要的结果吗?
是的。但在这种情况下,您需要两个计数器(一个用于A
类型,一个用于CCD类型),并且需要以线程安全的方式访问它们,例如使用Interlocked
类或使用lock
语句。所有线程,A
和B
类型,都将共享相同的AutoResetEvent
,但会增加它们自己类型的计数器。主线程可以监视每个计数器,并在两个计数器都达到其所需值时进行处理(A
计数器为1
,B
计数器为5
)。
我建议使用lock
语句方法,因为它更简单,并且可以避免完全使用AutoResetEvent
(lock
语句使用Monitor
类,它提供了一些类似于AutoResetEvent
的功能,同时还提供了确保计数器一致使用所需的同步。
除了你在评论中写的之外,你必须使用AutoResetEvent
(为什么?),所以我想你一直在使用Interlocked
(如果你不想充分利用lock
,那么使用CCD_19毫无意义)。
3.我不明白为什么所有的任务都打印在一起,我希望每个任务都能在完成时打印出来,现在一切都完成了。
因为你有个bug。您应该创建一个B
0实例,并使用它来确定每个任务的持续时间。您可以在创建每个任务的线程中计算持续时间,也可以同步访问(例如使用lock
)并在多个线程中使用相同的Random
对象。
不能为每个线程使用相同的种子值创建一个全新的Random
对象,因为这样每个线程(或者至少是其中的大块,取决于时间)最终都会得到完全相同的"随机"数作为其持续时间。
你可以看到所有的输出都在一起,因为这就是它发生的时候:所有的输出在一起。
(是的,如果你连续快速创建多个Random
对象,无论你自己显式使用DateTime.Now
,还是让Random
类来做,它们都会得到相同的种子。用于种子的勾号计数器更新频率不够高,无法同时运行的线程看到不同的值。)
4.以下代码线程安全吗?
有问题的代码:
while (numberOfBs < 5)
{
bResetEvent.WaitOne();
numberOfBs++;
}
…是线程安全的,因为执行该循环的线程与任何其他线程之间共享的唯一数据是AutoResetEvent
对象,而该对象本身就是线程安全的。
也就是说,对于"线程安全"的通常理解。我强烈建议你阅读Eric Lippert的文章你称之为"线程安全"的东西是什么?询问某个东西是否线程安全是一个你可能意识到的更复杂的问题。
特别是,虽然代码以通常的方式是线程安全的(即数据保持一致),但正如您所注意到的,在主线程对第一个调用做出反应之前,可能会有多个线程到达Set()
调用。因此,您可能会错过一些通知。
每次任务完成时,都会通知需要taks 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();
}
}