并行事件订阅者.net 4.5

时间:2014-12-19 10:08:08

标签: c# .net asynchronous .net-4.5

我正在尝试创建并行事件订阅者。这是我的第一次尝试:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EventStore.ClientAPI;

namespace Sandbox
{
    public class SomeEventSubscriber
    {
        private Position? _latestPosition;
        private readonly Dictionary<Type, Action<object>> _eventHandlerMapping;
        private IEventStoreConnection _connection;

        public Dictionary<Type, Action<object>> EventHandlerMapping
        {
            get { return _eventHandlerMapping; }
        }

        public SomeEventSubscriber()
        {
            _eventHandlerMapping = CreateEventHandlerMapping();
            _latestPosition = Position.Start;
        }

        public void Start()
        {
            ConnectToEventstore();
        }

        private void ConnectToEventstore()
        {
            _connection = EventStoreConnectionWrapper.Connect();
            _connection.Connected +=
            (sender, args) => _connection.SubscribeToAllFrom(_latestPosition, false, EventOccured, LiveProcessingStarted, HandleSubscriptionDropped);
        }

        private Dictionary<Type, Action<object>> CreateEventHandlerMapping()
        {
            return new Dictionary<Type, Action<object>>
            {
                {typeof (FakeEvent1), o => Handle(o as FakeEvent1)},
                {typeof (FakeEvent2), o => Handle(o as FakeEvent2)},
            };
        }

        private async Task Handle(FakeEvent1 eventToHandle)
        {
            SomethingLongRunning(eventToHandle);
        }

        private async Task Handle(FakeEvent2 eventToHandle)
        {
            SomethingLongRunning(eventToHandle);
        }

        private async Task SomethingLongRunning(BaseFakeEvent eventToHandle)
        {
            Console.WriteLine("Start Handling: " + eventToHandle.GetType());
            var task = Task.Delay(10000);
            await task;
            Console.WriteLine("Finished Handling: " + eventToHandle.GetType());
        }

        private void EventOccured(EventStoreCatchUpSubscription eventStoreCatchUpSubscription,
            ResolvedEvent resolvedEvent)
        {
            if (resolvedEvent.OriginalEvent.EventType.StartsWith("$") || resolvedEvent.OriginalEvent.EventStreamId.StartsWith("$"))
                return;

            var @event = EventSerialization.DeserializeEvent(resolvedEvent.OriginalEvent);
            if (@event != null)
            {
                var eventType = @event.GetType();
                if (_eventHandlerMapping.ContainsKey(eventType))
                {
                    var task = Task.Factory.StartNew(() => _eventHandlerMapping[eventType](event));
                    Console.WriteLine("The task is running asynchronously...");
                }
            }
            if (resolvedEvent.OriginalPosition != null) _latestPosition = resolvedEvent.OriginalPosition.Value;
        }

        private void HandleSubscriptionDropped(EventStoreCatchUpSubscription subscription, SubscriptionDropReason dropReason, Exception ex)
        {
            if (dropReason == SubscriptionDropReason.ProcessingQueueOverflow)
            {
                //TODO: Wait and reconnect probably with back off
            }

            if (dropReason == SubscriptionDropReason.UserInitiated)
                return;

            if (SubscriptionDropMayBeRecoverable(dropReason))
            {
                Start();
            }
        }

        private static bool SubscriptionDropMayBeRecoverable(SubscriptionDropReason dropReason)
        {
            return dropReason == SubscriptionDropReason.Unknown || dropReason == SubscriptionDropReason.SubscribingError ||
                   dropReason == SubscriptionDropReason.ServerError || dropReason == SubscriptionDropReason.ConnectionClosed;
        }

        private static void LiveProcessingStarted(EventStoreCatchUpSubscription eventStoreCatchUpSubscription)
        {

        }
    }
}

在您的专家意见中,这是一种有效的方法吗?你能否提出任何改进建议?

PS:

也许:

Task.Run(() => _eventHandlerMapping[eventType](@event));

会更好吗?

2 个答案:

答案 0 :(得分:1)

您只有一个EventOccured代表,您可以在EventStore发生的所有事件中收到通知 首先考虑在EventOccured内的预编码中运行与事件被触发的调度程序不同的调度程序 其次,您是否可以将此更改为abstract class并实现FakeEventBase,然后对其进行扩展并为每个FakeEvent类型创建单个实例。 这将是更清洁的解决方案 第三,考虑使用自定义ThreadScheduler来排队和运行这些Handle任务。 http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler(v=vs.110).aspx

编辑:
我会有一个类似下面的广播组,它知道操作何时完成并提升已完成的事件。

public class EventBroadcaster
{
    public event EventHandler SomeEventOccured;

    public async void DoLongRunningOperationAndRaiseFinishedEvent()
    {
        var waitingTask = Task.Delay(TimeSpan.FromSeconds(2));

        await waitingTask.ContinueWith(t => RaiseSomeEventOccured(), CancellationToken.None,
            TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
    }

    private void RaiseSomeEventOccured()
    {
        EventHandler handler = SomeEventOccured;
        if (handler != null) handler(this, EventArgs.Empty);
    }
}

然后是一个EventListener

public class EventListner
{
    private readonly string _id;

    public EventListner(string id)
    {
        _id = id;
    }

    public void ListenTo(EventBroadcaster broadcaster)
    {
        broadcaster.SomeEventOccured += OnSomeEventOccured;
    }

    private async void OnSomeEventOccured(object sender, EventArgs eventArgs)
    {
        var currentTime = DateTime.Now;
        Console.WriteLine("EventListner {0} received at {1}", _id,
            currentTime.ToString("dd-MM-yyyy HH:mm:ss.fffffff"));

        //Not required just to show this does not affect other instances.
        //await Task.Delay(TimeSpan.FromSeconds(5));
    }
}

然后这将是用于测试的Program.cs

public static class Program
{
    public static void Main(string[] args)
    {
        var broadcaster = new EventBroadcaster();

        var listners = new List<EventListner>();

        for (int i = 1; i < 10; i++)
        {
            var listner = new EventListner(i.ToString(CultureInfo.InvariantCulture));
            listner.ListenTo(broadcaster);
            listners.Add(listner);
        }

        broadcaster.DoLongRunningOperationAndRaiseFinishedEvent();

        Console.WriteLine("Waiting for operation to complete");

        Console.ReadLine();

    }
}

在此示例中,处理程序委托按订阅顺序逐个触发。

现在将Broadcaster中的代码修改为如下所示 注意:我已将方法签名从EventHandler更改为Action,以便于编码。

    private void RaiseSomeEventOccured()
    {
        Action handler = SomeEventOccured;
        if (handler != null)
        {
            var parallelOption = new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount };
            Parallel.Invoke(parallelOption, Array.ConvertAll(handler.GetInvocationList(), ConvertToAction));
            handler();
        }
    }

    private Action ConvertToAction(Delegate del)
    {
        return (Action)del;
    }

现在您将看到事件以随机顺序触发 我使用选项1获得了更好的表现 注意:始终使用TPLParallel编程,您需要确保在使用之前有一个好处。

答案 1 :(得分:1)

我真的没有看到创建并行事件订阅者的一点(如果我理解你的意图是正确的 - 能够并行运行事件处理程序,而不是像正常那样一个接一个地运行事件)。

如果事件处理程序本身显示,则表示意图并行运行 clear

像(非常原始)的东西。

void SomeEventHandler(object sender, EventArgs e)
{
    Task.Run(() =>
    {
       ... // some code to run in parallel
    });
}

你可能想要创建一种经理(老实说,我不知道如何占用所有核心,但我不认为这很复杂,我从来没有必要这样做),但请保持正常事件