如果我每次都从不同的线程调用事件,为啥会从同一个线程触发事件处理程序的多次执行?
Posted
技术标签:
【中文标题】如果我每次都从不同的线程调用事件,为啥会从同一个线程触发事件处理程序的多次执行?【英文标题】:Why are several executions of an eventhandler triggered from the same thread if I am invoking the event from a different thread each time?如果我每次都从不同的线程调用事件,为什么会从同一个线程触发事件处理程序的多次执行? 【发布时间】: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 实例如何,我都没有意识到它们都是来自同一个实例的执行。虽然它实际上不是预期的行为,但它看起来确实如此。
【参考方案1】:
代码没有按预期工作,因为事件处理程序的附件不正确。正如 Theodor Zoulias 和 Sean Skelly 在their comments 中指出的那样:
这一行有一个错误:rowOfDominoes[0].Fall += rowOfDominoes[i + 1].Collided;您将多个处理程序附加到同一个多米诺令牌的事件。
你有一个多米诺骨牌同时与其他四个相撞。如果您希望它们一次碰撞一个,请在添加事件处理程序时将 rowOfDominoes[0] 替换为 rowOfDominoes[i]。
通过此修复,它可以按预期工作。
【讨论】:
以上是关于如果我每次都从不同的线程调用事件,为啥会从同一个线程触发事件处理程序的多次执行?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 BackgroundWorker 的 Progress 事件运行在不同的线程上?
为啥 WCF 服务能够处理来自不同进程的调用而不是来自线程的调用
为啥 Application_Start 从不同的线程调用两次?
C++ volatile关键字(多线程中声明为易变值不稳定值,告诉程序每次都从内存读取,不被编译优化,防止被优化后变量异常)