【问题标题】:how to create small notification application using event and delegate如何使用事件和委托创建小型通知应用程序
【发布时间】:2015-08-03 23:19:38
【问题描述】:

我打算创建一个用于通知的小型演示应用程序。在这里,我有两个用户“User1”和“User2”,他们注册接收通知。 我想提供用户收到通知的时间。

所以,我的问题是,我怎样才能打发每个订阅者的时间并在那时引发 Notify 事件,以便每个订阅者都得到通知?

请参考打击代码。

namespace ConceptDemos
{

public class Notifier
{
    public delegate void NotificationHandler();

    public event NotificationHandler Notify;

    public Notifier()


    {

    }

    public void OnNotify()
    {
        if (this.Notify != null)
        {
            Notify();
        }
    }

}

public class User1
{
    public void WakeMeUp()
    {
        Console.WriteLine("Ringing User1's Alarm");
    }
}

public class User2
{
    public void StartMyTV()
    {
        Console.WriteLine("Starting User2's TV");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Notifier Notifier = new Notifier();
        User1 oUser1 = new User1();
        Notifier.Notify += oUser1.WakeMeUp;
        User2 oUser2 = new User2();
        Notifier.Notify += oUser2.StartMyTV;

        Notifier.OnNotify();

        Console.ReadLine();
    }
}
}

【问题讨论】:

  • 您是说要指定调用Notifier.OnNotify(); 的时间吗?
  • 是的,我想指定通知触发和订阅者收到通知的时间,但每个用户的时间可能不同,例如 user1 说“请在早上 7:00 叫醒我”和 User2说“请在下午 12:00 开始我的电视”

标签: c# delegates event-handling


【解决方案1】:

根据您的描述,您似乎希望在User1User2 上启动一个计时器。这可以通过几种不同的方式来完成。我已经完成的一种方法是使用支持同步和异步回调的自定义 Timer 类。

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// The Timer provides a means to start a timer with a callback that can be repeated over a set interval.
/// </summary>
/// <typeparam name="T">A generic Type</typeparam>
public sealed class Timer<T> : CancellationTokenSource, IDisposable
{
    /// <summary>
    /// The timer task
    /// </summary>
    private Task timerTask;

    /// <summary>
    /// How many times we have fired the timer thus far.
    /// </summary>
    private long fireCount = 0;

    /// <summary>
    /// Initializes a new instance of the <see cref="Timer{T}"/> class.
    /// </summary>
    /// <param name="callback">The callback.</param>
    /// <param name="state">The state.</param>
    public Timer(T state)
    {
        this.StateData = state;
    }

    /// <summary>
    /// Gets the state data.
    /// </summary>
    /// <value>
    /// The state data.
    /// </value>
    public T StateData { get; private set; }

    /// <summary>
    /// Gets a value indicating whether the engine timer is currently running.
    /// </summary>
    /// <value>
    /// <c>true</c> if this instance is running; otherwise, <c>false</c>.
    /// </value>
    public bool IsRunning { get; private set; }

    /// <summary>
    /// Starts the specified start delay.
    /// </summary>
    /// <param name="startDelay">The start delay in milliseconds.</param>
    /// <param name="interval">The interval in milliseconds.</param>
    /// <param name="numberOfFires">Specifies the number of times to invoke the timer callback when the interval is reached. Set to 0 for infinite.</param>
    public void Start(double startDelay, double interval, int numberOfFires, Action<T, Timer<T>> callback)
    {
        this.IsRunning = true;

        this.timerTask = Task
            .Delay(TimeSpan.FromMilliseconds(startDelay), this.Token)
            .ContinueWith(
                (task, state) => RunTimer(task, (Tuple<Action<T, EngineTimer<T>>, T>)state, interval, numberOfFires),
                Tuple.Create(callback, this.StateData),
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
                TaskScheduler.Default);
    }

    /// <summary>
    /// Starts the specified start delay.
    /// </summary>
    /// <param name="startDelay">The start delay in milliseconds.</param>
    /// <param name="interval">The interval in milliseconds.</param>
    /// <param name="numberOfFires">Specifies the number of times to invoke the timer callback when the interval is reached. Set to 0 for infinite.</param>
    public void StartAsync(double startDelay, double interval, int numberOfFires, Func<T, Timer<T>, Task> callback)
    {
        this.IsRunning = true;

        this.timerTask = Task
            .Delay(TimeSpan.FromMilliseconds(startDelay), this.Token)
            .ContinueWith(
                async (task, state) => await RunTimerAsync(task, (Tuple<Func<T, Timer<T>, Task>, T>)state, interval, numberOfFires),
                Tuple.Create(callback, this.StateData),
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
                TaskScheduler.Default);
    }

    /// <summary>
    /// Stops the timer for this instance. It is not 
    /// </summary>
    public void Stop()
    {
        if (!this.IsCancellationRequested)
        {
            this.Cancel();
        } 
        this.IsRunning = false;
    }

    /// <summary>
    /// Cancels the timer and releases the unmanaged resources used by the <see cref="T:System.Threading.CancellationTokenSource" /> class and optionally releases the managed resources.
    /// </summary>
    /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            this.IsRunning = false;
            this.Cancel();
        }

        base.Dispose(disposing);
    }

    private async Task RunTimer(Task task, Tuple<Action<T, EngineTimer<T>>, T> state, double interval, int numberOfFires)
    {
        while (!this.IsCancellationRequested)
        {
            // Only increment if we are supposed to.
            if (numberOfFires > 0)
            {
                this.fireCount++;
            }

            state.Item1(state.Item2, this);
            await PerformTimerCancellationCheck(interval, numberOfFires);
        }
    }

    private async Task RunTimerAsync(Task task, Tuple<Func<T, EngineTimer<T>, Task>, T> state, double interval, int numberOfFires)
    {
        while (!this.IsCancellationRequested)
        {
            // Only increment if we are supposed to.
            if (numberOfFires > 0)
            {
                this.fireCount++;
            }

            await state.Item1(state.Item2, this);
            await PerformTimerCancellationCheck(interval, numberOfFires);
        }
    }

    private async Task PerformTimerCancellationCheck(double interval, int numberOfFires)
    {
        // If we have reached our fire count, stop. If set to 0 then we fire until manually stopped.
        if (numberOfFires > 0 && this.fireCount >= numberOfFires)
        {
            this.Stop();
        }

        await Task.Delay(TimeSpan.FromMilliseconds(interval), this.Token).ConfigureAwait(false);
    }
}

现在你可以像这样连接起来:

// Create a new user and timer
User1 oUser1 = new User1();
var timer = new Timer(oUser1);

// Set the time to notify the user to 5 minutes from now.
var timeToNotify = DateTime.Now.AddMinutes(5);
new TimeSpan(timeToNotify.Hour, timeToNotify.Minute, timeToNotify.Second);

// Star the timer. In 5 minutes it will invoke the WakeMeUp() method.
timer.Start(
    startDelay: timeToNotify, 
    interval: 0, 
    numberOfFires: 1,
    callback: (user, timer) => user.WakeMeUp()); 

如果您想拥有某种观察者模式,请告诉我。有一种更好的方法可以通知用户某个事件已经发生并且用户需要对其做出反应。

【讨论】:

    【解决方案2】:

    另一种选择是使用 Pub/Sub 模式。使用此模式,您可以让每个 User 实例订阅发布者可以发送的特定发布消息。

    这种用法的一个例子是这样的:

    User1 类

    public class User1 : IDispose
    {
        private ISubscription subscription;
        public User1(NotificationManager notificationManager)
        {
            this.subscription = notificationManager.Subscribe<WakeUpNotification>(
                (notification, subscription) => this.WakeMeUp());
        }
    
        public void WakeMeUp()
        {
            Console.WriteLine("Ringing User1's Alarm");
        }
    
        public void Dispose()
        {
            this.subscription.Unsubscribe();
        }
    }
    

    User2 类

    public class User2 : IDispose
    {
        private ISubscription subscription;
        public User2(NotificationManager notificationManager)
        {
            this.subscription = notificationManager.Subscribe<StartMyTvNotification>(
                (notification, subscription) => this.StartMyTv());
        }
    
        public void StartMyTv()
        {
            Console.WriteLine("Ringing User1's Alarm");
        }
    
        public void Dispose()
        {
            this.subscription.Unsubscribe();
        }
    }
    

    某处的应用类

    var notificationManager = new NotificationManager();
    var user1 = new User1(notificationManager);
    var user2 = new User2(notificationManager);
    
    notificationManager.Publish(new WakeUpNotification(true));
    notificationManager.Publish(new TurnOnMyTvNotification(DateTime.Now));
    user1.Dispose();
    

    这可以与计时器结合使用,允许以不同的时间间隔发布消息。您可以使用 Timer 在与发布 TurnOnMyTvNotification 时不同的时间发布 WakeUpNotification

    发布/订阅实现

    以下是发布/订阅设置的实现。代码有很好的文档记录。

    INotificationCenter

    /// <summary>
    /// Provides a contract for Mediators to use when handling notifications between objects.
    /// </summary>
    public interface INotificationCenter
    {
        /// <summary>
        /// Sets up a new handler and returns it for subscription set up.
        /// </summary>
        /// <typeparam name="TMessageType">An IMessage implementation that the given handler will be provided when messages are dispatched</typeparam>
        /// <param name="handler">The handler used to process incoming messages.</param>
        /// <returns>Returns an ISubscription that can be used to unsubscribe.</returns>
        ISubscription Subscribe<TMessageType>(Action<TMessageType, ISubscription> callback, Func<TMessageType, bool> condition = null) where TMessageType : class, IMessage;
    
        /// <summary>
        /// Publishes the specified message.
        /// </summary>
        /// <typeparam name="TMessageType"></typeparam>
        /// <param name="message">The message.</param>
        void Publish<TMessageType>(TMessageType message) where TMessageType : class, IMessage;
    }
    

    通知管理器

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    
    /// <summary>
    /// The mediator for all messaging
    /// </summary>
    public class NotificationManager : INotificationCenter
    {
        /// <summary>
        /// Collection of subscribed listeners
        /// </summary>
        private ConcurrentDictionary<Type, List<ISubscription>> listeners =
            new ConcurrentDictionary<Type, List<ISubscription>>();
    
        /// <summary>
        /// Subscribe publications for the message type specified.
        /// </summary>
        /// <typeparam name="TMessageType">A concrete implementation of IMessage</typeparam>
        /// <returns></returns>
        public ISubscription Subscribe<TMessageType>(Action<TMessageType, ISubscription> callback, Func<TMessageType, bool> condition = null) where TMessageType : class, IMessage
        {
            ExceptionFactory.ThrowIf(
                callback == null,
                () => new ArgumentNullException(nameof(callback), "Callback must not be null when subscribing"));
    
            Type messageType = typeof(TMessageType);
    
            // Create our key if it doesn't exist along with an empty collection as the value.
            if (!listeners.ContainsKey(messageType))
            {
                listeners.TryAdd(messageType, new List<ISubscription>());
            }
    
            // Add our notification to our listener collection so we can publish to it later, then return it.
            // TODO: Move instancing the Notification in to a Factory.
            var handler = new Notification<TMessageType>();
            handler.Register(callback, condition);
            handler.Unsubscribing += this.Unsubscribe;
    
            List<ISubscription> subscribers = listeners[messageType];
            lock (subscribers)
            {
                subscribers.Add(handler);
            }
    
            return handler;
        }
    
        /// <summary>
        /// Publishes the specified message to all subscribers
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="message">The message.</param>
        public void Publish<T>(T message) where T : class, IMessage
        {
            ExceptionFactory.ThrowIf(
                message == null,
                () => new ArgumentNullException(nameof(message), "You can not publish a null message."));
    
            if (!listeners.ContainsKey(typeof(T)))
            {
                return;
            }
    
            foreach (INotification<T> handler in listeners[typeof(T)])
            {
                handler.ProcessMessage(message);
            }
        }
    
        /// <summary>
        /// Unsubscribes the specified handler by removing their handler from our collection.
        /// </summary>
        /// <typeparam name="T">The message Type you want to unsubscribe from</typeparam>
        /// <param name="subscription">The subscription to unsubscribe.</param>
        private void Unsubscribe(NotificationArgs args)
        {
            // If the key doesn't exist or has an empty collection we just return.
            // We will leave the key in there for future subscriptions to use.
            if (!listeners.ContainsKey(args.MessageType) || listeners[args.MessageType].Count == 0)
            {
                return;
            }
    
            // Remove the subscription from the collection associated with the key.
            List<ISubscription> subscribers = listeners[args.MessageType];
            lock (subscribers)
            {
                subscribers.Remove(args.Subscription);
            }
    
            args.Subscription.Unsubscribing -= this.Unsubscribe;
        }
    }
    

    我订阅

    /// <summary>
    /// Provides a contract to Types wanting to subscribe to published messages 
    /// with conditions and a callback.
    /// </summary>
    public interface ISubscription
    {
        /// <summary>
        /// Occurs when the subscription is being unsubscribed.
        /// </summary>
        event Action<NotificationArgs> Unsubscribing;
    
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="ISubscription"/> is active.
        /// </summary>
        bool IsActive { get; }
    
        /// <summary>
        /// Unsubscribes the registerd callbacks from receiving notifications.
        /// </summary>
        /// <param name="notificationCenter">The notification center.</param>
        void Unsubscribe();
    }
    

    INotification

    using System;
    
    /// <summary>
    /// Processes a subscription message.
    /// </summary>
    /// <typeparam name="TMessageType">The type of the message type.</typeparam>
    public interface INotification<TMessageType> : ISubscription where TMessageType : class, IMessage
    {
        /// <summary>
        /// Registers the specified action for callback when a notification is fired for T.
        /// </summary>
        /// <param name="callback">The message being posted along with the subscription registered to receive the post.</param>
        /// <returns></returns>
        void Register(
            Action<TMessageType, ISubscription> callback,
            Func<TMessageType, bool> condition = null);
    
        /// <summary>
        /// Processes the message, invoking the registered callbacks if their conditions are met.
        /// </summary>
        /// <param name="message">The message.</param>
        void ProcessMessage(TMessageType message);
    }
    

    IMessage

    /// <summary>
    /// Allows for receiving the content of a message
    /// </summary>
    public interface IMessage
    {
        /// <summary>
        /// Gets the content.
        /// </summary>
        /// <returns>Returns the content of the message</returns>
        object GetContent();
    }
    
    /// <summary>
    /// Allows for receiving the content of a message
    /// </summary>
    /// <typeparam name="TContent">The type of the content.</typeparam>
    public interface IMessage<TContent> : IMessage where TContent : class
    {
        /// <summary>
        /// Gets the content of the message.
        /// </summary>
        TContent Content { get; }
    }
    

    WakeMeUpNotification

    /// <summary>
    /// Provides methods for dispatching notifications to subscription handlers
    /// </summary>
    /// <typeparam name="TMessageType">The type of the message type.</typeparam>
    public class WakeMeUpNotification : IMessage<DateTime> where TContentType : class
    {
        public WakeMeUpNotification(DateTime timeToWakeUp)
        {
            this.Content = timeToWakeUp
        }
        /// <summary>
        /// Gets the content of the message.
        /// </summary>
        public DateTime Content { get; protected set; }
    
        /// <summary>
        /// Gets the content of the message.
        /// </summary>
        public DateTime GetContent()
        {
            return this.Content;
        }
    
        /// <summary>
        /// Gets the content.
        /// </summary>
        object IMessage.GetContent()
        {
            return this.GetContent();
        }
    }
    

    StartMyTvNotification

    /// <summary>
    /// Provides methods for dispatching notifications to subscription handlers
    /// </summary>
    /// <typeparam name="TMessageType">The type of the message type.</typeparam>
    public class StartMyTvNotification : IMessage<bool> where TContentType : class
    {
        public StartMyTvNotification(bool isOn)
        {
            this.Content = isOn;
        }
    
        /// <summary>
        /// Gets the content of the message.
        /// </summary>
        public bool Content { get; protected set; }
    
        /// <summary>
        /// Gets the content of the message.
        /// </summary>
        public bool GetContent()
        {
            return this.Content;
        }
    
        /// <summary>
        /// Gets the content.
        /// </summary>
        object IMessage.GetContent()
        {
            return this.GetContent();
        }
    }
    

    通知

    using System;
    
    /// <summary>
    /// Handles chat message subscriptions
    /// </summary>
    internal class Notification<TMessage> : INotification<TMessage> where TMessage : class, IMessage
    {
        /// <summary>
        /// The callbacks invoked when the handler processes the messages.
        /// </summary>
        private Action<TMessage, ISubscription> callback;
    
        /// <summary>
        /// The conditions that must be met in order to fire the callbacks.
        /// </summary>
        private Func<TMessage, bool> condition;
    
        /// <summary>
        /// Occurs when the subscription is being unsubscribed.
        /// </summary>
        public event Action<NotificationArgs> Unsubscribing;
    
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="ISubscription" /> is active.
        /// </summary>
        public bool IsActive { get; protected set; }
    
        /// <summary>
        /// Registers a callback for when a chat message is published by the MessageCenter
        /// </summary>
        /// <param name="processor">The message.</param>
        /// <returns></returns>
        public void Register(
            Action<TMessage, ISubscription> processor,
            Func<TMessage, bool> condition)
        {
            this.callback = processor;
            this.condition = condition;
            this.IsActive = true;
        }
    
        /// <summary>
        /// Unsubscribes the handler from notifications. This cleans up all of the callback references and conditions.
        /// </summary>
        public void Unsubscribe()
        {
            this.callback = null;
            this.condition = null;
    
            try
            {
                this.OnUnsubscribing();
            }
            finally
            {
                this.IsActive = false;
            }
        }
    
        /// <summary>
        /// Processes the message by verifying the callbacks can be invoked, then invoking them.
        /// </summary>
        /// <param name="message">The message.</param>
        public void ProcessMessage(TMessage message)
        {
            if (this.condition != null && !this.condition(message))
            {
                this.callback(message, this);
                return;
            }
    
            this.callback(message, this);
        }
    
        /// <summary>
        /// Called when the notification is being unsubscribed from.
        /// </summary>
        protected virtual void OnUnsubscribing()
        {
            var handler = this.Unsubscribing;
            if (handler == null)
            {
                return;
            }
    
            handler(new NotificationArgs(this, typeof(TMessage)));
        }
    }
    

    NotificationArgs

    public class NotificationArgs
    {
        public NotificationArgs(ISubscription subscription, Type messageType)
        {
            this.Subscription = subscription;
            this.MessageType = messageType;
        }
    
        public ISubscription Subscription { get; private set; }
    
        public Type MessageType { get; private set; }
    }
    

    【讨论】:

      【解决方案3】:

      这是我要替换 Notifier.OnNotify(); 行的代码:

      var timer = new System.Timers.Timer();
      timer.AutoReset = false;
      timer.Interval =
          new DateTime(2015, 5, 22, 15, 6, 0)
              .Subtract(DateTime.Now)
              .TotalMilliseconds;
      
      System.Timers.ElapsedEventHandler handler = null;
      handler = (s, e) =>
      {
          Notifier.OnNotify();
          timer.Elapsed -= handler;
          timer.Stop();
          timer.Dispose();
      };
      timer.Elapsed += handler;
      
      timer.Start();
      

      显然,您需要更改日期以适应您希望事件触发的时间。

      我已经包含了一个相当完整的示例,说明如何触发事件然后事后清理。您应该始终进行这种清理。

      现在这将在后台线程上触发事件。如果您需要在 UI 线程上触发事件,那么您需要执行适当的调用以将其到达那里。

      我想我会做的唯一其他评论是,如果您有任何多线程,您的OnNotify 实现会有一个小错误。应该是这样的:

      public void OnNotify()
      {
          var notify = this.Notify;
          if (notify != null)
          {
              notify();
          }
      }
      

      这停止了this.Notify 中的委托被if 和调用之间的另一个线程删除的问题。作为一种良好做法,养成以这种方式编写代码的习惯是一个好主意。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-03-16
        • 1970-01-01
        相关资源
        最近更新 更多