【发布时间】:2020-08-09 15:35:30
【问题描述】:
为了了解有关 .NET 中事件的基础知识,我制作了一个控制台应用程序,该应用程序对一排 5 个多米诺骨牌令牌进行建模,当用手指按下第一个令牌时,这些令牌会掉落。每对连续令牌之间的交互由当前令牌中的事件 Fall 和下一个令牌中的事件处理程序 Collided 处理。当一个令牌掉落时,它会调用一个 Fall 事件,下一个令牌由 Collided 委托订阅。每个令牌需要 1000 毫秒才能下落。
程序的第一个版本,在单线程中运行,需要 5000 毫秒才能按预期完成:
using System;
using System.Threading;
namespace SimpleRubeGoldbergMachine
{
public class Finger { }
public class DominoToken
{
public event EventHandler Fall;
public void KickOff()
{
//Collides with your finger and kicks off the chain reaction
this.Collided(new Finger(), EventArgs.Empty);
}
public void Collided(object sender, EventArgs e)
{
var objectType = sender.GetType().Name;
Console.WriteLine($"A {objectType} has bumped into the domino token.");
Console.WriteLine("The token falls!");
Thread.Sleep(1000);
//On falling, the domino token collides with the next token
this.OnFalling(EventArgs.Empty);
}
private void OnFalling(EventArgs e)
{
Fall?.Invoke(this, EventArgs.Empty);
}
}
public class Program
{
private static void Main(string[] args)
{
var rowOfDominoes = new[]
{
new DominoToken(),
new DominoToken(),
new DominoToken(),
new DominoToken(),
new DominoToken()
};
//Attach the Collided delegate of each domino Token to the Fall event of the previous Token
for (var i = 0; i < 4; i++)
{
rowOfDominoes[0].Fall += rowOfDominoes[i + 1].Collided;
}
//Kick-off
rowOfDominoes[0].KickOff();
}
}
}
控制台输出:
A Finger has bumped into the domino token.
The token falls!
A DominoToken has bumped into the domino token.
The token falls!
A DominoToken has bumped into the domino token.
The token falls!
A DominoToken has bumped into the domino token.
The token falls!
A DominoToken has bumped into the domino token.
The token falls!
当我尝试在不同的线程中运行每个事件处理程序时,我的问题就出现了。请注意,使用不同线程的原因是我试图从一个可以并行运行(即下降)的事件中启动几行多米诺骨牌,但这部分代码不是重现我的问题所必需的。
我从 Collided 事件处理程序中为每个标记启动一个新线程。令我惊讶的是,该程序只需要 2000 毫秒即可完成。我期望事件处理程序的每次执行都是从不同的线程产生的,但它们都是从同一个线程产生的。出于这个原因,同时发生了几个 Fall 事件(这不是预期的行为)。
我为每次 Collided() 的执行添加了带有 spawner 线程 ID 和当前线程的跟踪,以便解决问题:
using System;
using System.Threading;
namespace SimpleRubeGoldbergMachine
{
public class Finger { }
public class DominoToken
{
public event EventHandler<int> Fall;
public void KickOff()
{
//Collides with your finger and kicks off the chain reaction
this.Collided(new Finger(), Thread.CurrentThread.ManagedThreadId);
}
public void Collided(object sender, int threadId)
{
var thread = new Thread(() =>
{
var objectType = sender.GetType().Name;
Console.WriteLine($"A {objectType} has bumped into the domino token.");
Console.WriteLine("The token falls!");
Console.WriteLine("Spawner Thread: " + threadId);
Console.WriteLine("Current Thread: " + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(1000);
//On falling, the domino token collides with the next token
this.OnFalling(Thread.CurrentThread.ManagedThreadId);
});
thread.IsBackground = false;
thread.Start();
}
private void OnFalling(int threadId)
{
Fall?.Invoke(this, threadId);
}
}
public class Program
{
private static void Main(string[] args)
{
var rowOfDominoes = new[]
{
new DominoToken(),
new DominoToken(),
new DominoToken(),
new DominoToken(),
new DominoToken()
};
//Attach the Collided delegate of each domino Token to the Fall event of the previous Token
for (var i = 0; i < 4; i++)
{
rowOfDominoes[0].Fall += rowOfDominoes[i + 1].Collided;
}
//Kick-off
rowOfDominoes[0].KickOff();
}
}
}
预期的控制台输出:
A Finger has bumped into the domino token.
The token falls!
Spawner Thread: 1
Current Thread: 2
A DominoToken has bumped into the domino token.
The token falls!
Spawner Thread: 2
Current Thread: 3
A DominoToken has bumped into the domino token.
The token falls!
Spawner Thread: 3
Current Thread: 4
A DominoToken has bumped into the domino token.
The token falls!
Spawner Thread: 4
Current Thread: 5
A DominoToken has bumped into the domino token.
The token falls!
Spawner Thread: 5
Current Thread: 6
实际控制台输出:
A Finger has bumped into the domino token.
The token falls!
Spawner Thread: 1
Current Thread: 5
A DominoToken has bumped into the domino token.
The token falls!
A DominoToken has bumped into the domino token.
The token falls!
Spawner Thread: 5
Current Thread: 6
A DominoToken has bumped into the domino token.
The token falls!
Spawner Thread: 5
Current Thread: 9
A DominoToken has bumped into the domino token.
The token falls!
Spawner Thread: 5
Current Thread: 8
Spawner Thread: 5
Current Thread: 7
为什么 DominoToken 的不同实例的多个委托是从同一个线程产生的 (5)?第一次交互(finger-1st token,#1)和第二次交互(2nd token-3rd token,thread #5)之间的生成线程实际上是不同的
【问题讨论】:
-
我认为这行有一个错误:
rowOfDominoes[0].Fall += rowOfDominoes[i + 1].Collided;您正在将多个处理程序附加到同一个多米诺令牌的事件。 -
@TheodorZoulias 是对的 - 所有其他四个多米诺骨牌都在处理来自同一个多米诺骨牌的事件。你有一个多米诺骨牌同时与其他四个相撞。如果您希望它们一次碰撞一个,请在添加事件处理程序时将 rowOfDominoes[0] 替换为 rowOfDominoes[i]。
-
@Theodor Zoulias 和 Sean Skelly,感谢您指出我的愚蠢错误。现在可以了。
-
现在你必须弄清楚为什么程序的第一个版本(单线程),尽管 bug 可以按预期工作。顺便说一句,很抱歉投反对票。你在这个问题上做了很多工作。所以你有一个(虚拟的)+1 我的努力。 :-)
-
别担心,我明白。我现在无法调试它,但据我所知,附加到单个事件的多个事件处理程序是按顺序执行的,因此 Collide 一个接一个地执行 5 次是预期的行为。因为 Collide 事件处理程序的每次执行都会产生完全相同的输出,无论 dominoToken 实例如何,我都没有意识到它们都是来自同一个实例的执行。虽然它实际上不是预期的行为,但它看起来确实如此。
标签: c# .net multithreading events