#c# #.net #events
#c# #.net #Мероприятия
Вопрос:
Я создал обработчик событий, который просто возвращает список объектов, которые я получаю от веб-службы по завершении вызова.
Теперь я пошел дальше и запустил приложение в режиме отладки и обнаружил, что при первом вызове события оно работает отлично, но сразу после его завершения событие запускается во второй раз. Я проверил и абсолютно уверен, что не вызываю событие более одного раза в классе получателя.
Это мой первый опыт создания пользовательских обработчиков событий внутри моих приложений, поэтому я не совсем уверен, что реализация на 100% точна.
Есть идеи о том, что может быть причиной этого? Является ли способ, которым я создал обработчик событий, точным?
Это класс DataHelper
public class DataHelper
{
public delegate void DataCalledEventHandler(object sender, List<DataItem> dateItemList);
public event DataCalledEventHandler DataCalled;
public DataHelper()
{
}
public void CallData()
{
List<DataItem> dataItems = new List<DataItem>();
//SOME CODE THAT RETURNS DATA
DataCalled(this, dataItems);
}
}
Здесь я подписался на свое событие:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
GetNewDataItems();
}
private void GetNewDataItems()
{
try
{
DataHelper dataHelper = new DataHelper();
dataHelper.CallData();
dataHelper.DataCalled = new DataHelper.DataCalledEventHandler(dataHelper_DataCalled);
}
catch
{
//Handle any errors
}
}
}
void dataHelper_DataCalled(object sender, List<DataItem> dataItemsList)
{
//Do something with results
//NOTE: THIS IS WHERE THE EXCEPTION OCCURS WHEN EVENT IS FIRED FOR SECOND TIME
}
Комментарии:
1. Не могли бы вы показать код, в котором вы подписываетесь на событие?
2. @WouterdeKort Конечно, дай мне секунду.
3. Почему вы подписываетесь на событие
DataCalled
после вызоваCallData
? Этот способCallData
вызовет исключение, потому что наDataCalled
событие нет подписчиков, если вы не подписались в другом месте.4. Фрагмент кода очень странный, трудно представить, почему вы подписываетесь на событие после вызова метода. Он никогда не должен срабатывать. Публикуйте фактический код, не редактируйте.
Ответ №1:
Вероятно, вы добавили делегата дважды, возможно ли это?
В этом случае проблема не в том, кто вызывает делегата, а в том, кто добавляет делегата к событию.
Вероятно, вы сделали что-то вроде…
private Class1 instance1;
void callback(...)
{
}
void myfunction()
{
this.instance1.DataCalled = this.callback;
this.instance1.DataCalled = this.callback;
}
Если нет, попробуйте добавить точку останова, где вы подписываетесь на событие, и посмотрите, вызывается ли оно дважды.
В качестве примечания, вы всегда должны проверять значение null при вызове события, если нет подписчика, вы можете получить исключение NullReferenceException. Я бы также посоветовал вам использовать переменную для хранения делегата события, чтобы избежать риска сбоя многопоточности.
public void CallData()
{
List<DataItem> dataItems = new List<DataItem>();
var handler = this.DataCalled;
if (handler != null)
handler(this, dataItems);
}
Редактировать: поскольку теперь я вижу код, очевидно, что каждый раз, когда вы вызываете метод GetNewDataItems, вы каждый раз приписываете событие.
Таким образом, вы подписываетесь только один раз, например, в конструкторе, или сохраняете свою переменную где-нибудь, или отменяете регистрацию события по завершении.
Этот код также содержит вероятную утечку памяти: каждый раз, когда вы добавляете делегат, вы поддерживаете как экземпляр, содержащий событие, так и экземпляр, содержащий подписанный метод, по крайней мере, до тех пор, пока на оба не будут даны ссылки.
Вы можете попробовать сделать что-то вроде этого…
void dataHelper_DataCalled(object sender, List<DataItem> dataItemsList)
{
// Deregister the event...
(sender as Class1).DataCalled -= dataHelper_DataCalled;
//Do something with results
}
Однако таким образом вы должны убедиться, что, если во время регистрации события не возникнет исключения, событие будет запущено или у вас снова возникнут утечки памяти.
Возможно, вместо события вам нужен просто делегат. Конечно, вы должны установить для своего поля делегата значение null, когда хотите освободить делегата.
// in data helper class
private DataHelper.DataCalledEventHandler myFunctor;
public void CallData(DataHelper.DataCalledEventHandler functor)
{
this.myFunctor = functor;
//SOME CODE THAT RETURNS DATA
}
// when the call completes, asynchronously...
private void WhenTheCallCompletes()
{
var functor = this.myFunctor;
if (functor != null)
{
this.myFunctor = null;
List<DataItem> dataItems = new List<DataItem>();
functor(this, dataItems);
}
}
// in your function
... dataHelper.CallData(this.dataHelper_DataCalled); ...
Комментарии:
1. Нет, к сожалению, похоже, что это не так. Изначально я думал, что его нужно добавить дважды. Я добавил еще немного кода сверху, если вы хотите взглянуть.
2. Спасибо за отличный отзыв. Я смог реализовать все, что вы упомянули. Что касается проблемы, я понял это. Сначала мне пришлось преодолеть ошибку «Исключение с нулевой ссылкой», используя ваш пример. Затем я понял, что выполняю вызовы с использованием HttpWebRequest в моем классе DataHelper. В этом случае вы должны использовать BeginInvoke для маршалирования данных в потоке пользовательского интерфейса. Как ни странно, когда возникло исключение «Недопустимый доступ между потоками», это происходило внутри метода, который вызвал событие после завершения операции.
Ответ №2:
Приведенные ниже строки в вашем коде должны быть перевернуты. Это
Эти строки
dataHelper.CallData();
dataHelper.DataCalled = new DataHelper.DataCalledEventHandler(dataHelper_DataCalled);
Должно быть:
dataHelper.DataCalled = new DataHelper.DataCalledEventHandler(dataHelper_DataCalled);
dataHelper.CallData();
Потому что сначала вам нужно подключить обработчик событий, а затем вызвать другие методы для объекта, которые могут вызвать событие
Комментарии:
1. Да, верно! Не заметил этого 🙂 По этой причине передача его в качестве параметра в функции кажется более естественным выбором.
2. @Ankur Да, это плохая привычка. Я никогда не прекращал это делать, потому что он никогда не выдавал исключение для меня. Но определенно не кажется правильным делать это в обратном направлении. Я буду вводить его таким образом в будущем. Спасибо за совет.
3. Ну, это не только плохая привычка, поскольку это код, который выполняется в другом потоке, он может завершиться ДО того, как вы подпишетесь на событие, поэтому ваш обратный вызов может случайно пропустить выполнение. Это опасно и непредсказуемо.