Написание функции, которая должна ожидать обратного вызова от события - PullRequest
2 голосов
/ 19 мая 2011

Проблема

Рассмотрим следующий код:

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);
    }
}

Следующий код проблематичен на мой взгляд по нескольким причинам:

  1. Существуетразделение кода, которое по логике должно быть сгруппировано вместе - я ожидал бы, что Console.WriteLine("In middle of long process: " + _workId); появится рядом с Console.WriteLine("At end of long process: " + _workId);, и, тем не менее, они оба появляются в отдельных функциях.функция внутри DoWork , однако я вынужден сделать ее полевой функцией, чтобы иметь возможность доступа к этой информации изнутри обратного вызова.Каждый дополнительный параметр или возвращаемое значение также станет элементом поля.

Альтернативные решения

Существует несколько решений, которые могут уменьшить количество проблем в этом коде:

Инкапсуляция функции и ее обратного вызова в другом классе

Одной из проблем, которые я описал выше, были члены поля, которые не обязательно связаны с классом владельца в целом,но вместо этого специфичен для функции 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 Ответ

1 голос
/ 19 мая 2011

ваш класс EventSyncr настолько странен, что вы должны получить за это дополнительную награду, просто шучу;)

Но на самом деле вам нужно всего лишь создать AutoResetEvent и ждать его в конце кода, используя WaitHandle.WaitAll.

В конце вашего асинхронного метода вы должны сообщить 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});
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...