Почему несколько исполнений обработчика событий запускаются из одного потока, если я каждый раз вызываю событие из другого потока? - PullRequest
0 голосов
/ 26 апреля 2020

Чтобы узнать основы событий в. 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)

1 Ответ

0 голосов
/ 26 апреля 2020

Код не работает должным образом, потому что приложение обработчиков событий не правильно. Как указано Теодором Зулиасом и Шоном Скелли в их комментариях :

, в этой строке есть ошибка: rowOfDominoes [0] .Fall + = rowOfDominoes [i + 1] .Collided; Вы присоединяете несколько обработчиков к событию одного токена домино.

У вас есть одно Domino, сталкивающееся с четырьмя другими одновременно. Если вы хотите, чтобы они конфликтовали по одному, замените rowOfDominoes [0] на rowOfDominoes [i] при добавлении обработчиков событий.

С этим исправлением все работает как положено.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...