#c# #.net #events
#c# #.net #Мероприятия
Вопрос:
Проблема
Рассмотрим следующий код:
class A
{
private int _workId;
public void DoWork()
{
_workId = new Random().Next(100);
Console.WriteLine("Starting a long process ID: " _workId);
LongProcess longProcess = new LongProcess();
longProcess.StartWork();
longProcess.OnWorkMiddle = OnLongProcessWorkMiddle;
Console.WriteLine("At end of long process: " _workId);
longProcess.OnWorkMiddle -= OnLongProcessWorkMiddle;
}
private void OnLongProcessWorkMiddle(object sender, EventArgs e)
{
Console.WriteLine("In middle of long process: " _workId);
}
}
Следующий код, на мой взгляд, проблематичен по нескольким причинам:
- Существует разделение кода, который по логике должен быть сгруппирован вместе — я бы ожидал, что
Console.WriteLine("In middle of long process: " _workId);
он появится рядом сConsole.WriteLine("At end of long process: " _workId);
, и все же они оба отображаются в отдельных функциях. _workId
по всем правилам должна быть функция внутри DoWork, однако я вынужден сделать ее field functions, чтобы иметь возможность доступа к этой информации изнутри обратного вызова. Каждый дополнительный параметр или возвращаемое значение также станут элементом поля.
Альтернативные решения
Есть несколько решений, которые могли бы уменьшить количество проблем в этом коде:
Инкапсулирование функции и ее обратного вызова в другой класс
Одной из проблем, которые я описал выше, были элементы поля, которые не обязательно связаны с классом owner в целом, но вместо этого специфичны для функции DoWork и ее обратных вызовов. Инкапсулируя функцию в ее собственном классе, мы удаляем этот ненужный беспорядок и группируем его в свою собственную логическую группу. Недостатком является то, что это требует создания класса из того, что, по сути, должно было быть функцией. Это также не решает проблему № 1.
Использование анонимных делегатов для сохранения в одной функции
Мы могли бы переписать код следующим образом:
public void DoWork()
{
var workId = new Random().Next(100);
EventHandler onLongProcessWorkMiddle = delegate { Console.WriteLine("In middle of long process: " workId); };
Console.WriteLine("Starting a long process ID: " workId);
LongProcess longProcess = new LongProcess();
longProcess.StartWork();
longProcess.OnWorkMiddle = onLongProcessWorkMiddle;
Console.WriteLine("At end of long process: " workId);
longProcess.OnWorkMiddle -= onLongProcessWorkMiddle;
}
Теперь нам больше не нужны какие-либо элементы поля, и мы можем красиво сохранить все в одной функции. Однако с точки зрения удобочитаемости я нахожу код довольно слабым, поскольку код (или, скорее, его делегат), который выполняется позже, появляется сверху.
Использование полу-универсального вспомогательного класса
В этом решении я написал полу-универсальный вспомогательный класс для синхронизации выполнения. Это позволяет мне писать код следующим образом:
public void DoWork()
{
var workId = new Random().Next(100);
LongProcess longProcess = new LongProcess();
var syncr = new EventSyncr();
syncr.Sync(
delegate { longProcess.OnWorkMiddle = syncr.EventCallback; },
delegate { longProcess.StartWork(); },
delegate { Console.WriteLine("In middle of long process: " workId); },
delegate { longProcess.OnWorkMiddle -= syncr.EventCallback; });
Console.WriteLine("At end of long process: " workId);
}
И вот код для вспомогательного класса:
/// <summary>
/// Used to synchronize code that waits for a callback from a function.
/// </summary>
class EventSyncr
{
readonly AutoResetEvent _eventCallbackEvent = new AutoResetEvent(false);
private Action _actionAtEvent;
/// <summary>
/// Executes the syncing process. This function is blocked until the event is called back (once), then it executes the 'actionAtEvent' segment and
/// after unsubscription, exists.
/// </summary>
/// <param name="eventSubscription">The passed delegate must subscribe the event to EventSyncr.EventCallback.</param>
/// <param name="eventCausingAction">The event causing action.</param>
/// <param name="actionAtEvent">The action at event.</param>
/// <param name="eventUnsubscription">Unsubscribe the event that was subscribed in the first parameter</param>
public void Sync(Action eventSubscription, Action eventCausingAction, Action actionAtEvent, Action eventUnsubscription)
{
_actionAtEvent = actionAtEvent;
try
{
eventSubscription();
eventCausingAction();
_eventCallbackEvent.WaitOne();
}
finally
{
eventUnsubscription();
}
}
public void EventCallback(object sender, EventArgs e)
{
try
{
_actionAtEvent();
}
finally
{
_eventCallbackEvent.Set();
}
}
}
Хотя это немного многословно, мне кажется, что это решает все проблемы, о которых я говорил выше:
Код отображается именно так, как он будет выполняться, в своем логическом порядке, и все дополнительные свойства являются автономными.
Вопросы
Мне любопытно, что вы думаете о проблеме и возможных решениях. Какой вариант кажется лучшим? Есть ли лучший способ решения проблемы?
Я знаю, что в C # 5 функция await обеспечит гораздо лучшее решение, но мы еще не совсем там : (.
Спасибо!
Ответ №1:
ваш класс EventSyncr настолько странный, что вы должны получить дополнительную награду за это, просто шучу 😉
Но на самом деле вам нужно только создать AutoResetEvent и дождаться его в конце вашего кода, используя WaitHandle .Подождите все.
В конце вашего асинхронного метода вы должны подать сигнал AutoResetEvent, и ваш основной поток узнает, что ваш асинхронный процесс только что завершился.
public void DoWork()
{
var workId = new Random().Next(100);
Console.WriteLine("Starting a long process ID: " workId);
LongProcess longProcess = new LongProcess();
longProcess.StartWork();
longProcess.OnWorkMiddle = ()=>{ Console.WriteLine("In middle of long process: " workId);
longProcess.OnWorkEnd = ()=>{
Console.WriteLine("At the end of a long process");
autoEvent.Set();
};
WaitHandle.WaitAll(new []{longProcess.autoEvent});
}
Комментарии:
1. @VitalyB у вас появилась идея с вашей функцией «Sync», но то, что вы делаете с делегированием в методах 1 и 2, просто не предназначено для выполнения таким образом. @BitKFu очень хороший ответ! e: Рассмотрите асинхронную потоковую передачу как еще одну хорошую идею 😉
2. Думаю, благодаря комплименту :). Длительный процесс. Start() в коде, который я упомянул, является блокирующей функцией, поэтому я не могу использовать AutoResetEvent для синхронизации потоков. Но вы правы, если бы она выполнялась (и отправляла события) в другом потоке, это было бы жизнеспособным решением. AutoResetEvent в EventSyncr действительно является частью более общего лечения… Возможно, мне следовало бы удалить это, потому что я больше ориентирую вопросы на однопоточные сценарии.
3. Что касается вашего кода — Почему вы не отписались от событий? Это что, ошибка? Или это потому, что вы предполагаете, что ‘longProcess’ в любом случае будет GC’d?