С# добавление и удаление событий из таймера

Я пытаюсь добавлять и удалять события из таймера, и у меня есть следующий код:

Timer myTimer = new Timer(); // Windows.Forms Timer

public void addEvent(MyDelegate ev)
{
    myTimer.Tick += new EventHandler(ev);
}

public void removeEvent(MyDelegate ev)
{
    myTimer.Tick -= new EventHandler(ev);
}

Я не знаю. Если я сделаю что-нибудь глупое, пытаясь добавить и удалить делегатов таким образом, я смогу добавить делегатов и заставить их срабатывать, как и ожидалось. Однако, когда я пытаюсь удалить события, они продолжают запускать таймеры Tick.

Может ли кто-нибудь увидеть что-то явно неправильное?


person TK.    schedule 16.02.2009    source источник


Ответы (7)


Я считаю, что этот код:

myTimer.Tick -= new EventHandler(ev);

создает новый объект EventHandler. Он никогда не удалит существующий EventHandler. Чтобы получить желаемую функциональность, вы должны передавать EventHandlers, а не MyDelegates, в методы добавления и удаления:

Timer myTimer = new Timer(); // Windows.Forms Timer

public void addEvent(EventHandler ev)
{
    myTimer.Tick += ev;
}

public void removeEvent(EventHandler ev)
{
    myTimer.Tick -= ev;
}

Вызывающий код должен будет отслеживать добавленные обработчики событий, чтобы он мог передать тот же объект EventHandler, когда придет время отказаться от подписки.

person atoumey    schedule 16.02.2009
comment
Я знал, что у меня там был момент блокирования кодеров! Вы правы, говоря, что использование EventHandlers вместо Delegates работает. Спасибо за вашу помощь! - person TK.; 17.02.2009

Исходный код работает нормально, пока MyDelegate 'ev', переданный в addEvent и removeEvent, является одним и тем же экземпляром объекта (например, если есть поле MyDelegate уровня класса, содержащее экземпляр, или если вы следуете совету нескольких других здесь и сохраните объект(ы) MyDelegate в словаре).

Я подозреваю, что проблема в том, что код, вызывающий addEvent и removeEvent, передает новые экземпляры MyDelegate, указывающие на какой-то метод обработчика, например:

addEvent(new MyDelegate(this.HandlerMethod));
// ... do some stuff
removeEvent(new MyDelegate(this.HandlerMethod));

В этом случае addEvent и removeEvent создают EventHandler делегатов, которые указывают на разные адреса методов, даже если эти делегаты, в свою очередь, указывают на один и тот же метод (this.HandlerMethod). Это связано с тем, что делегаты EventHandler, созданные add и remove, указывают на метод MyDelegate.Invoke() в разных экземплярах MyDelegate, а не непосредственно на адрес this.HandlerMethod.

person Jeff Sternal    schedule 16.02.2009
comment
В дополнение к подозрениям шута, это также может произойти, если вызывающая сторона просто передает имя метода, которое соответствует подписи делегата, например, addEvent(this.MyCallback);. В этом случае делегат создается неявно, что приводит к описанной проблеме. - person Matt Davis; 17.02.2009

Ваша проблема связана с наличием вспомогательных методов для этого. Без них работает как положено, с ними не знает что отцеплять.

Чтобы исправить это, вам нужно будет поддерживать словарь со значением, являющимся EventHandler, созданным в методе перехвата, чтобы вы могли удалить это значение позже.

Что-то типа:

var handlers = new Dictionary<MyDelegate, EventHandler>();

public void addEvent(MyDelegate ev)
{
    var handler = new EventHandler(ev);
    handlers.Add(ev, handler);
    myTimer.Tick += handler;
}

public void removeEvent(MyDelegate ev)
{
    myTimer.Tick -= handlers[ev];
}

Вы должны добавить соответствующие проверки, если элемент существует.

Вы также можете изменить тип параметра, и он будет работать так, как ожидалось.

public void addEvent(EventHandler ev)
{
    myTimer.Tick += ev;
}

public void removeEvent(EventHandler ev)
{
    myTimer.Tick -= ev;
}

addEvent(new EventHandler(...));
removeEvent(new EventHandler(...));
person Samuel    schedule 16.02.2009

Я не знаю, что вы делаете неправильно, но обычный подход, который я бы использовал для таймеров, заключается в том, чтобы подписаться на событие Tick, а затем отключить таймер, когда вы не хотите получать события, повторно включив, когда вы делаете.

Может не помочь вам, если к событию подключено более одного обработчика событий, но, надеюсь, будет полезно.

person Sam Meldrum    schedule 16.02.2009

Вы должны просто иметь возможность отказаться от подписки, сославшись на имя вашего метода обработки даже следующим образом:

public void removeEvent(MyDelegate ev)
{
    myTimer.Tick -= ev as EventHandler;
}
person Factor Mystic    schedule 16.02.2009
comment
К сожалению, это приводит к ошибке компилятора. Невозможно неявно преобразовать тип «myDelegate» в «System.EventHandler». - person TK.; 16.02.2009
comment
Это не имело бы никакого значения. Если вы опускаете новый EventHandler(...) или подобные конструкторы, они добавляются при компиляции. - person Samuel; 16.02.2009

Это должно работать:

private void timer_Tick(object sender, EventArgs e)
{
    try
    {
        // Disallow re-entry
        timer.Tick -= timer_Tick;
        . . .
    }
    finally
    {
        timer.Tick += timer_Tick;
    }
}
person B. Clay Shannon    schedule 02.03.2016

При добавлении и удалении обработчиков событий вы каждый раз создаете новую оболочку для своего делегата. Таким образом, в вашем методе удаления он пытается удалить новый объект EventHandler, который никогда не добавлялся в качестве прослушивателя события.

Если вы хотите продолжать использовать этот тип настройки, вы можете вставить свои обработчики событий в словарь. В методе addEvent вставьте только что созданный EventHandler в свой словарь, а в методе removeEvent извлеките EventHandler из словаря и удалите его вместо создания нового экземпляра.

person John Conrad    schedule 16.02.2009