Чтобы узнать основы событий в. 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 мс до конца sh. Я ожидал, что каждое выполнение обработчика событий будет порождаться из другого потока, но вместо этого все они будут порождаться из одного и того же потока . По этой причине несколько событий Fall происходят одновременно (что не является желаемым поведением).
Я добавил трассировки с идентификатором потока spawner и текущего потока для каждого выполнения Collided (), чтобы устранить проблему:
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)? Нисходящий поток фактически отличается между первым взаимодействием (палец-1-й токен, # 1) и вторым взаимодействием (2-й токен-3-й токен, поток # 5)