编写一个必须等​​待来自事件回调的函数

时间:2011-05-19 08:23:45

标签: c# .net events

问题

请考虑以下代码:

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. 有一个代码分离,逻辑应该分组为togeter - 我希望Console.WriteLine("In middle of long process: " + _workId);出现在Console.WriteLine("At end of long process: " + _workId);旁边,然而,它们都出现在单独的函数中。
  2. _workId应该是 DoWork 中的所有功能,但是,我不得不使其成为一个字段函数,以便能够从回调内部访问该信息。每个附加参数或返回值也将成为字段成员。
  3. 替代解决方案

    有一些解决方案可以减少此代码中的问题数量:

    将函数及其回调封装在不同的类中

    我上面描述的一个问题是字段成员,它们不一定与所有者类相关,而是特定于函数 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中,等待功能将提供更好的解决方案,但我们还没有完全实现:(。

    谢谢!

1 个答案:

答案 0 :(得分:1)

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