[C#] Akka 중급 2-3 | 'Scheduler' 를 사용한 지연 메시지 보내기

참조

소개

  • 스케줄러 사용방법에 대해 학습합니다.
  • 액터를 이용한 게시-구독 패턴 구현 방법에 대해 학습합니다.

Key Concepts / Background

  • 액터가 앞으로 뭔가를 하게 하려면 어떻게 해야 하나요? 그리고 그 액터가 미래에 반복적으로 무언가 하기를 원한다면 어떨까요?
  • 아마도 당신은 액터가 주기적으로 정보를 가져 오거나, 시스템 내의 다른 액터에 대한 상태를 가끔 핑(ping) 하기를 원할 수 있습니다.
  • Akka.NET은 이러한 작업을 수행하기 위한 메커니즘을 제공합니다.

스케줄러(Scheduler) 란?

  • ActorSystem.Scheduler는 모든 ActorSystem 내의 싱글톤으로, 앞으로 액터에게 메시지를 보낼 수 있도록 스케줄을 설정할 수 있습니다.
  • 스케줄러는 일회성 메시지와 반복성 메시지를 모두 보낼 수 있습니다.

Scheduler는 어떻게 사용하나요?

  • 앞서 언급했듯이, 액터에게 일회성 또는 반복 메시지를 예약할 수 있습니다.
  • 액터에게 메시지를 보내는 대신 앞으로 발생할 동작을 예약할 수도 있습니다.

ActorSystem을 통해 Scheduler에 액세스

  • Scheduler는 다음과 같이 ActorSystem을 통해 액세스 해야 합니다.
//inside Main.cs we have direct handle to the ActorSystem
var system = ActorSystem.Create("MyStystem");
system.Scheduler.ScheduleTellOnce(TimeSpan.FromMinutes(30),
                                    someActor,
                                    someMessage, ActorRefs.Nobody);

//but inside an actor, we access the ActorSystem via the ActorContext
Context.System.Scheduler.ScheduleTellOnce(TimeSpan.FromMunutes(30),
                                            someActor,
                                            someMessage, ActorRefs.Nobody);

ScheduleTellOnce() 를 사용하여 일회성 메시지 예약

  • 액터 중 하나에서 30분 후에 RSS 피드에서 최신 콘텐츠를 가져 오도록 하고 싶다고 가정해보겠습니다.
  • 이를 위해 IScheduler.SchuduleTellOnce()를 사용할 수 있습니다.
var system = ActorSystem.Create("MySystem");
var someActor = system.ActorOf<SomeActor>("someActor");
var someMessage = new FetchFeed() {Url = ...};

//schedule the message
system
    .Scheduler
    .ScheduleTellOnce(TimeSpan.FromMinutes(30), someActor, someMessage, ActorRefs.Nobody);
  • someActor는 30분 후에 someMessage를 수신합니다.

ScheduleTellRepeatedly()를 사용하여 반복 메시지 예약

  • 이 메시지가 30분마다 한번 배달되도록 예약 하려면 어떻게 해야 합니까?
  • 이를 위해 다음 IScheduler.ScheduleTellRepeatedly()를 오버로드해 사용할 수 있습니다.
var system = ActorSystem.Create("MySystem");
var someActor = system.ActorOf<SomeActor>("someActor");
var someMessage = new FetchFeed() {Url = ...};

//schudule recurring message
system
    .Scheduler
    .SchuduleTellRepeatedly(TimeSpan.FromMinutes(30), // initial delay of 30 min
                            TimeSpan.FromMunutes(30), // recur every 30 minutes
                            someActor, someMessage, ActorRefs.Nobody);

예약된 메시지를 취소하려면 어떻게 하나요?

  • 예약되거나 반복되는 메시지를 취소해야 하는 경우는 어떻게 됩니까?
  • ICancelable을 사용하여 생성할 수 있는 Cancelable 인스턴스를 사용합니다.
  • 먼저 메시지를 취소할 수 있도록 예약해야 합니다.
  • 메시지가 취소 가능하면, ICancelable에 대한 핸들에서 Cancel() 을 반드시 호출해야 합니다.
  • 그렇지 않으면 메시지 취소가 전달되지 않습니다.
var system = ActorSystem.Create("MySystem");
var cancellation = new Cancelable(system.Scheduler);
var someActor = system.ActorOf<SomeActor>("someActor");
var someMessage = new FetchFeed() {Url = ...};

// first, set up the message so that it can be canceled
system
    .Scheduler
    .ScheduleTellRepeatedly(TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(30),
                            someActor, someMessage, ActorRefs.Nobody, cancellation); //add cancellation support

// here we actually cancel the message and prevent it from being delivered
cancellation.Cancel();

대안 : ScheduleTellRepeatedlyCancelable을 사용하여 ICancelable 작업 가져 오기

  • Akka.NET v1.0에서 소개한 새로운 IScheduler 메서드 중 하나는 ScheduleTellRepeatedlyCancelable 확장 메서드입니다.
  • 이 확장 메서드는 반복 메시지에 대한 ICancelable 인스턴스를 생성하는 프로세스를 나타내고, 간단히 ICancelable을 반환합니다.
var system = ActorSystem.Create("MySystem");
var someActor = system.ActorOf<SomeActor>("someActor");
var someMessage = new FetchFeed() {Url = ...};

// cancellable recurring message send created automatically
var cahcellation = system
    .Scheduler
    .ScheduleTellRepeatedlyCancelable(TimeSpan.FromMinutes(30), 
                                        TimeSpan.FromMinutes(30),
                                        someActor,
                                        someMessage,
                                        ActorRefs.Nobody);

// here we actually cancel the message and prevent it from being delivered
cancellation.Cancel();
  • 이것은 이전 예제에 대한 좀 더 간결한 대안이며, 이 부트캠프에서는 사용하지 않더라도 앞으로 사용하는 것이 좋습니다.

예약된 메시지의 타이밍은 얼마나 정확한가요?

  • 예약된 메시지는 우리가 접한 모든 사용 사례에 대해 충분히 정확합니다.
  • 그렇긴 하지만, 우리가 알고 있는 부정확한 상황이 두 가지가 있습니다.
    1. 예약 된 메시지는 CLR 스레드풀에 예약되고 내부적으로 Task.Delay를 사용합니다. CLR 스레드풀에 높은 로드가 있는 경우 작업이 계획보다 조금 늦게 완료될 수 있습니다. 작업이 예상 한 밀리 초에 정확히 실행된다는 보장은 없습니다.
    2. 스케줄링 요구 사항이 15밀리초 미만의 정밀도를 요구하는 경우 스케줄러가 충분히 정확하지 않습니다. Windows, OSX 또는 Linux와 같은 일반적인 운영 체제도 마찬가지입니다. 이는 ~15ms가 Windows 및 기타 일반 OS가 시스템 클럭을 업데이트하는 간격이기 때문에, 이러한 OS는 자체 시스템 클럭보다 정확한 타이밍을 지원할 수 없기 때문입니다.

Schedule과 ScheduleOnce 의 다양한 오버로드는 무엇이 있나요?

  • 다음은 메시지 예약에 사용할 수 있는 모든 오버로드 옵션입니다.
  • ScheduleTellRepeatedly 의 오버로드
    • 반복 메시지를 예약하기 위해 수행할 수 있는 다양한 API 호출이 있습니다.
  • ScheduleTellOnce의 오버로드
    • 일회성 메시지를 예약하기 위해 만들 수 있는 다양한 API 호출이 있습니다.

Akka.NET 액터로 Pub/Sub 를 어떻게 수행하나요?

  • 사실 아주 간단합니다.
  • 많은 사람들은 이것이 매우 복잡할 것으로 기대하고 더 많은 코드가 관련되지 않았는지 의심합니다.
  • Akka.NET 액터를 사용하는 pub / sub에 대한 마법은 없습니다.
  • 말 그대로 이렇게 간단 할 수 있습니다.
public class PubActor : ReceiveActor
{
    //HashSet automatically eliminates duplicates
    private HashSet<IActorRef> _subscribers;

    PubActor()
    {
        _subscribers = new HashSet<IActorRef>();

        Receive<Subscribe>(sub =>
        {
            _subscribers.Add(sub.IActorRef);
        });

        Receive<MessageSubscribersWant>(message => {
            //notify each subscriber
            foreach(var sub in _subscribers)
            {
                sub.Tell(message);
            }
        });

        Receive<unsubscribe>(unsub => {
            _subscribers.Remove(unsub.IActorRef);
        });
    }
}
  • Pub/sub는 Akka.NET에서 구현하기에 매우 간단한 것으로, 이에 잘 맞는 시나리오가 있을 때 정기적으로 사용할 수 있는 패턴입니다.
  • 이제 Scheduler가 작동하는 방식에 익숙해 졌음으로, 이를 사용하여 차트 UI를 반응형으로 만들 수 있습니다.

실습

  • ButtonToggleActor.cs
using Akka.Actor;
using System.Windows.Forms;

namespace Akka_Test.Actors
{
    public class ButtonToggleActor : UntypedActor
    {
        public class Toggle { }

        private readonly CounterType _myCounterType;
        private bool _isToggledOn;
        private readonly Button _myButton;
        private readonly IActorRef _coordinatorActor;

        public ButtonToggleActor(IActorRef coordinatorActor, Button myButton,
                CounterType myCounterType, bool isToggledOn = false)
        {
            _coordinatorActor = coordinatorActor;
            _myButton = myButton;
            _isToggledOn = isToggledOn;
            _myCounterType = myCounterType;
        }

        protected override void OnReceive(object message)
        {
            if (message is Toggle && _isToggledOn)
            {
                // toggle is currently on

                // stop watching this counter
                _coordinatorActor.Tell(
                    new PerformanceCounterCoordinatorActor.Unwatch(_myCounterType));

                FlipToggle();
            }
            else if (message is Toggle && !_isToggledOn)
            {
                // toggle is currently off

                // start watching this counter
                _coordinatorActor.Tell(
                    new PerformanceCounterCoordinatorActor.Watch(_myCounterType));

                FlipToggle();
            }
            else
            {
                Unhandled(message);
            }
        }

        private void FlipToggle()
        {
            // flip the toggle
            _isToggledOn = !_isToggledOn;

            // change the text of the button
            _myButton.Text = $"{_myCounterType.ToString().ToUpperInvariant()} {(_isToggledOn ? "ON" : "OFF")}";
        }
    }
}
  • ChartingActor.cs
using Akka.Actor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms.DataVisualization.Charting;

namespace Akka_Test.Actors
{
    public class ChartingActor : ReceiveActor
    {
        public const int MaxPoints = 250;
        public int xPosCounter = 0;

        public class InitializeChart
        {
            public Dictionary<string, Series> InitialSeries { get; private set; }
            public InitializeChart(Dictionary<string, Series> initialSeries)
            {
                InitialSeries = initialSeries;
            }
        }

        public class AddSeries
        {
            public Series Series { get; private set; }
            public AddSeries(Series series)
            {
                Series = series;
            }
        }

        public class RemoveSeries
        {
            public string SeriesName { get; private set; }
            public RemoveSeries(string seriesName)
            {
                SeriesName = seriesName;
            }
        }

        private readonly Chart _chart;
        private Dictionary<string, Series> _seriesIndex;
        public ChartingActor(Chart chart) : this(chart, new Dictionary<string, Series>())
        {

        }

        public ChartingActor(Chart chart, Dictionary<string, Series> seriesIndex)
        {
            _chart = chart;
            _seriesIndex = seriesIndex;

            Receive<InitializeChart>(ic => HandleInitialize(ic));
            Receive<AddSeries>(addSeries => HandleAddSeries(addSeries));
            Receive<RemoveSeries>(removeSeries => HandleRemoveSeries(removeSeries));
            Receive<Metric>(metric => HandleMetrics(metric));

        }

        private void HandleAddSeries(AddSeries series)
        {
            if (!string.IsNullOrEmpty(series.Series.Name) &&
        !_seriesIndex.ContainsKey(series.Series.Name))
            {
                _seriesIndex.Add(series.Series.Name, series.Series);
                _chart.Series.Add(series.Series);
                SetChartBoundaries();
            }
        }

        private void HandleRemoveSeries(RemoveSeries series)
        {
            if (!string.IsNullOrEmpty(series.SeriesName) &&
                _seriesIndex.ContainsKey(series.SeriesName))
            {
                var seriesToRemove = _seriesIndex[series.SeriesName];
                _seriesIndex.Remove(series.SeriesName);
                _chart.Series.Remove(seriesToRemove);
                SetChartBoundaries();
            }
        }

        private void HandleMetrics(Metric metric)
        {
            if (!string.IsNullOrEmpty(metric.Series) &&
                _seriesIndex.ContainsKey(metric.Series))
            {
                var series = _seriesIndex[metric.Series];
                series.Points.AddXY(xPosCounter++, metric.CounterValue);
                while (series.Points.Count > MaxPoints) series.Points.RemoveAt(0);
                SetChartBoundaries();
            }
        }



        private void HandleInitialize(InitializeChart ic)
        {
            if (ic.InitialSeries != null)
            {
                // swap the two series out
                _seriesIndex = ic.InitialSeries;
            }

            // delete any existing series
            _chart.Series.Clear();

            // set the axes up
            var area = _chart.ChartAreas[0];
            area.AxisX.IntervalType = DateTimeIntervalType.Number;
            area.AxisY.IntervalType = DateTimeIntervalType.Number;

            SetChartBoundaries();

            // attempt to render the initial chart
            if (_seriesIndex.Any())
            {
                foreach (var series in _seriesIndex)
                {
                    // force both the chart and the internal index to use the same names
                    series.Value.Name = series.Key;
                    _chart.Series.Add(series.Value);
                }
            }

            SetChartBoundaries();

        }

        private void SetChartBoundaries()
        {
            double maxAxisX, maxAxisY, minAxisX, minAxisY = 0.0d;
            var allPoints = _seriesIndex.Values.SelectMany(series => series.Points).ToList();
            var yValues = allPoints.SelectMany(point => point.YValues).ToList();
            maxAxisX = xPosCounter;
            minAxisX = xPosCounter - MaxPoints;
            maxAxisY = yValues.Count > 0 ? Math.Ceiling(yValues.Max()) : 1.0d;
            minAxisY = yValues.Count > 0 ? Math.Floor(yValues.Min()) : 0.0d;
            if (allPoints.Count > 2)
            {
                var area = _chart.ChartAreas[0];
                area.AxisX.Minimum = minAxisX;
                area.AxisX.Maximum = maxAxisX;
                area.AxisY.Minimum = minAxisY;
                area.AxisY.Maximum = maxAxisY;
            }

        }
    }
}
  • ChartingMessage.cs
using Akka.Actor;

namespace Akka_Test.Actors
{
    public class GatherMetrics { }

    public class Metric
    {
        public string Series { get; private set; }
        public float CounterValue { get; private set; }

        public Metric(string series, float counterValue)
        {
            CounterValue = counterValue;
            Series = series;
        }
    }

    public enum CounterType
    {
        Cpu,
        Memory,
        Disk
    }

    public class SubscribeCounter
    {
        public CounterType Counter { get; private set; }
        public IActorRef Subscriber { get; private set; }

        public SubscribeCounter(CounterType counter, IActorRef subscriber)
        {
            Subscriber = subscriber;
            Counter = counter;
        }
    }

    public class UnsubscribeCounter
    {
        public CounterType Counter { get; private set; }
        public IActorRef Subscriber { get; private set; }

        public UnsubscribeCounter(CounterType counter, IActorRef subscriber)
        {
            Subscriber = subscriber;
            Counter = counter;
        }
    }

    public class ChartingMessages
    {
    }
}
  • PerformanceCounterActor.cs
using Akka.Actor;
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Akka_Test.Actors
{
    public class PerformanceCounterActor : UntypedActor
    {
        private readonly string _seriesName;
        private readonly Func<PerformanceCounter> _performanceCounterGenerator;
        private PerformanceCounter _counter;

        private readonly HashSet<IActorRef> _subscriptions;
        private readonly ICancelable _cancelPublishing;

        public PerformanceCounterActor(string seriesName, Func<PerformanceCounter> performanceCounterGenerator)
        {
            _seriesName = seriesName;
            _performanceCounterGenerator = performanceCounterGenerator;
            _subscriptions = new HashSet<IActorRef>();
            _cancelPublishing = new Cancelable(Context.System.Scheduler);
        }

        protected override void PreStart()
        {
            //create a new instance of the performance counter
            _counter = _performanceCounterGenerator();
            Context.System.Scheduler.ScheduleTellRepeatedly(
                TimeSpan.FromMilliseconds(250),
                TimeSpan.FromMilliseconds(250),
                Self,
                new GatherMetrics(),
                Self,
                _cancelPublishing
                );
        }

        protected override void PostStop()
        {
            try
            {
                //terminate the scheduled task
                _cancelPublishing.Cancel(false);
                _counter.Dispose();
            }
            catch
            {
                // we don't care about additional "ObjectDisposed" exceptions
            }
            finally
            {
                base.PostStop();
            }
        }

        protected override void OnReceive(object message)
        {
            if(message is GatherMetrics)
            {
                //publish latest counter value to all subscribers
                var metric = new Metric(_seriesName, _counter.NextValue());
                foreach (var sub in _subscriptions)
                    sub.Tell(metric);
            }
            else if(message is SubscribeCounter)
            {
                // add a subscription for this counter
                var sc = message as SubscribeCounter;
                _subscriptions.Add(sc.Subscriber);
            }
            else if(message is UnsubscribeCounter)
            {
                //remove a subscription from this counter
                var uc = message as UnsubscribeCounter;
                _subscriptions.Remove(uc.Subscriber);
            }
        }
    }
}
  • PerformanceCounterCoordinatorActor
using Akka.Actor;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms.DataVisualization.Charting;

namespace Akka_Test.Actors
{
    public class PerformanceCounterCoordinatorActor : ReceiveActor
    {
        public class Watch
        {
            public Watch(CounterType counter)
            {
                Counter = counter;
            }

            public CounterType Counter { get; private set; }
        }

        public class Unwatch
        {
            public Unwatch(CounterType counter)
            {
                Counter = counter;
            }

            public CounterType Counter { get; private set; }
        }

        private static readonly Dictionary<CounterType, Func<PerformanceCounter>>
            CounterGenerators = new Dictionary<CounterType, Func<PerformanceCounter>>()
        {
            {CounterType.Cpu, () => new PerformanceCounter("Processor",
                "% Processor Time", "_Total", true)},
            {CounterType.Memory, () => new PerformanceCounter("Memory",
                "% Committed Bytes In Use", true)},
            {CounterType.Disk, () => new PerformanceCounter("LogicalDisk",
                "% Disk Time", "_Total", true)},
        };

        private static readonly Dictionary<CounterType, Func<Series>> CounterSeries =
            new Dictionary<CounterType, Func<Series>>()
        {
            {CounterType.Cpu, () =>
            new Series(CounterType.Cpu.ToString()){
                 ChartType = SeriesChartType.SplineArea,
                 Color = Color.DarkGreen}},
            {CounterType.Memory, () =>
            new Series(CounterType.Memory.ToString()){
                ChartType = SeriesChartType.FastLine,
                Color = Color.MediumBlue}},
            {CounterType.Disk, () =>
            new Series(CounterType.Disk.ToString()){
                ChartType = SeriesChartType.SplineArea,
                Color = Color.DarkRed}},
        };

        private Dictionary<CounterType, IActorRef> _counterActors;

        private IActorRef _chartingActor;

        public PerformanceCounterCoordinatorActor(IActorRef chartingActor) :
            this(chartingActor, new Dictionary<CounterType, IActorRef>())
        {
        }

        public PerformanceCounterCoordinatorActor(IActorRef chartingActor,
            Dictionary<CounterType, IActorRef> counterActors)
        {
            _chartingActor = chartingActor;
            _counterActors = counterActors;

            Receive<Watch>(watch =>
            {
                if (!_counterActors.ContainsKey(watch.Counter))
                {
                    // create a child actor to monitor this counter if
                    // one doesn't exist already
                    var counterActor = Context.ActorOf(Props.Create(() =>
                        new PerformanceCounterActor(watch.Counter.ToString(),
                                CounterGenerators[watch.Counter])));

                    // add this counter actor to our index
                    _counterActors[watch.Counter] = counterActor;
                }

                // register this series with the ChartingActor
                _chartingActor.Tell(new ChartingActor.AddSeries(
                    CounterSeries[watch.Counter]()));

                // tell the counter actor to begin publishing its
                // statistics to the _chartingActor
                _counterActors[watch.Counter].Tell(new SubscribeCounter(watch.Counter,
                    _chartingActor));
            });

            Receive<Unwatch>(unwatch =>
            {
                if (!_counterActors.ContainsKey(unwatch.Counter))
                {
                    return; // noop
                }

                // unsubscribe the ChartingActor from receiving any more updates
                _counterActors[unwatch.Counter].Tell(new UnsubscribeCounter(
                    unwatch.Counter, _chartingActor));

                // remove this series from the ChartingActor
                _chartingActor.Tell(new ChartingActor.RemoveSeries(
                    unwatch.Counter.ToString()));
            });
        }
    }
}
  • App.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
   <configSections>
      <section name="akka" type="Akka.Configuration.Hocon.AkkaConfigurationSection, Akka" />
   </configSections>
   <akka>
      <hocon>
         <![CDATA[
          akka {
            actor{
              deployment{
                #used to configure our ChartingActor
                /charting{
                  dispatcher = akka.actor.synchronized-dispatcher #causes ChartingActor to run on the UI thread for WinForms
                }
              }
            }
          }
      ]]>
      </hocon>
   </akka>
</configuration>
  • ChartDataHelper.cs
    using Akka.Util;
    using System;
    using System.Linq;
    using System.Windows.Forms.DataVisualization.Charting;
    

namespace Akka_Test
{
///


/// Helper class for creating random data for chart plots
///

public static class ChartDataHelper
{
public static Series RandomSeries(string seriesName, SeriesChartType type = SeriesChartType.Line, int points = 100)
{
var series = new Series(seriesName) { ChartType = type };
foreach (var i in Enumerable.Range(0, points))
{
var rng = ThreadLocalRandom.Current.NextDouble();
series.Points.Add(new DataPoint(i, 2.0 * Math.Sin(rng) + Math.Sin(rng / 4.5)));
}
series.BorderWidth = 3;
return series;
}
}
}


* **Form1.cs**

```csharp
using Akka.Actor;
using Akka.Util.Internal;
using Akka_Test.Actors;
using System.Collections.Generic;
using System.Windows.Forms;

namespace Akka_Test
{
    public partial class Form1 : Form
    {
        private IActorRef _chartActor;
        private readonly AtomicCounter _seriesCounter = new AtomicCounter(1);

        private IActorRef _coordinatorActor;
        private Dictionary<CounterType, IActorRef> _toggleActors = new Dictionary<CounterType,
            IActorRef>();


        public Form1()
        {
            InitializeComponent();
        }

        private void Main_Load(object sender, System.EventArgs e)
        {
            _chartActor = Program.ChartActors.ActorOf(Props.Create(() =>
            new ChartingActor(sysChart)), "charting");
            _chartActor.Tell(new ChartingActor.InitializeChart(null)); //no initial series

            _coordinatorActor = Program.ChartActors.ActorOf(Props.Create(() =>
            new PerformanceCounterCoordinatorActor(_chartActor)), "counters");

            //CPU Button toggle actor
            _toggleActors[CounterType.Cpu] = Program.ChartActors.ActorOf(
       Props.Create(() => new ButtonToggleActor(_coordinatorActor, btnCpu,
       CounterType.Cpu, false))
       .WithDispatcher("akka.actor.synchronized-dispatcher"));

            //Memory Button toggle actor
            _toggleActors[CounterType.Memory] = Program.ChartActors.ActorOf(
       Props.Create(() => new ButtonToggleActor(_coordinatorActor, btnMemory,
        CounterType.Memory, false))
        .WithDispatcher("akka.actor.synchronized-dispatcher"));

            //DISK Butto toggle actor
            _toggleActors[CounterType.Disk] = Program.ChartActors.ActorOf(
       Props.Create(() => new ButtonToggleActor(_coordinatorActor, btnDisk,
       CounterType.Disk, false))
       .WithDispatcher("akka.actor.synchronized-dispatcher"));

            //Set the CPU toggle to ON so we start getting some data
            _toggleActors[CounterType.Cpu].Tell(new ButtonToggleActor.Toggle());
        }

        private void Main_FormClosing(object sender, FormClosingEventArgs e)
        {
            //shut down the charting actor
            _chartActor.Tell(PoisonPill.Instance);

            //shut down the ActorSystem
            Program.ChartActors.Terminate();
        }

        private void btnCpu_Click(object sender, System.EventArgs e)
        {
            _toggleActors[CounterType.Cpu].Tell(new ButtonToggleActor.Toggle());
        }

        private void btnMemory_Click(object sender, System.EventArgs e)
        {
            _toggleActors[CounterType.Memory].Tell(new ButtonToggleActor.Toggle());
        }

        private void btnDisk_Click(object sender, System.EventArgs e)
        {
            _toggleActors[CounterType.Disk].Tell(new ButtonToggleActor.Toggle());
        }
    }
}
  • Form1.designer.cs

namespace Akka_Test
{
    partial class Form1
    {
        /// <summary>
        /// 필수 디자이너 변수입니다.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// 사용 중인 모든 리소스를 정리합니다.
        /// </summary>
        /// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form 디자이너에서 생성한 코드

        /// <summary>
        /// 디자이너 지원에 필요한 메서드입니다. 
        /// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
        /// </summary>
        private void InitializeComponent()
        {
            System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea3 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
            System.Windows.Forms.DataVisualization.Charting.Legend legend3 = new System.Windows.Forms.DataVisualization.Charting.Legend();
            System.Windows.Forms.DataVisualization.Charting.Series series3 = new System.Windows.Forms.DataVisualization.Charting.Series();
            this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
            this.panel2 = new System.Windows.Forms.Panel();
            this.panel1 = new System.Windows.Forms.Panel();
            this.sysChart = new System.Windows.Forms.DataVisualization.Charting.Chart();
            this.btnCpu = new System.Windows.Forms.Button();
            this.btnMemory = new System.Windows.Forms.Button();
            this.btnDisk = new System.Windows.Forms.Button();
            this.tableLayoutPanel1.SuspendLayout();
            this.panel2.SuspendLayout();
            this.panel1.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this.sysChart)).BeginInit();
            this.SuspendLayout();
            // 
            // tableLayoutPanel1
            // 
            this.tableLayoutPanel1.ColumnCount = 2;
            this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 82.625F));
            this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 17.375F));
            this.tableLayoutPanel1.Controls.Add(this.panel2, 1, 0);
            this.tableLayoutPanel1.Controls.Add(this.panel1, 0, 0);
            this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
            this.tableLayoutPanel1.Name = "tableLayoutPanel1";
            this.tableLayoutPanel1.RowCount = 1;
            this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
            this.tableLayoutPanel1.Size = new System.Drawing.Size(800, 450);
            this.tableLayoutPanel1.TabIndex = 0;
            // 
            // panel2
            // 
            this.panel2.Controls.Add(this.btnDisk);
            this.panel2.Controls.Add(this.btnMemory);
            this.panel2.Controls.Add(this.btnCpu);
            this.panel2.Dock = System.Windows.Forms.DockStyle.Fill;
            this.panel2.Location = new System.Drawing.Point(664, 3);
            this.panel2.Name = "panel2";
            this.panel2.Size = new System.Drawing.Size(133, 444);
            this.panel2.TabIndex = 1;
            // 
            // panel1
            // 
            this.panel1.Controls.Add(this.sysChart);
            this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.panel1.Location = new System.Drawing.Point(3, 3);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(655, 444);
            this.panel1.TabIndex = 0;
            // 
            // sysChart
            // 
            chartArea3.Name = "ChartArea1";
            this.sysChart.ChartAreas.Add(chartArea3);
            this.sysChart.Dock = System.Windows.Forms.DockStyle.Fill;
            legend3.Name = "Legend1";
            this.sysChart.Legends.Add(legend3);
            this.sysChart.Location = new System.Drawing.Point(0, 0);
            this.sysChart.Name = "sysChart";
            series3.ChartArea = "ChartArea1";
            series3.Legend = "Legend1";
            series3.Name = "Series1";
            this.sysChart.Series.Add(series3);
            this.sysChart.Size = new System.Drawing.Size(655, 444);
            this.sysChart.TabIndex = 0;
            this.sysChart.Text = "chart1";
            // 
            // btnCpu
            // 
            this.btnCpu.Font = new System.Drawing.Font("굴림", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
            this.btnCpu.Location = new System.Drawing.Point(2, 259);
            this.btnCpu.Name = "btnCpu";
            this.btnCpu.Size = new System.Drawing.Size(130, 54);
            this.btnCpu.TabIndex = 0;
            this.btnCpu.Text = "CPU (ON)";
            this.btnCpu.UseVisualStyleBackColor = true;
            this.btnCpu.Click += new System.EventHandler(this.btnCpu_Click);
            // 
            // btnMemory
            // 
            this.btnMemory.Font = new System.Drawing.Font("굴림", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
            this.btnMemory.Location = new System.Drawing.Point(2, 319);
            this.btnMemory.Name = "btnMemory";
            this.btnMemory.Size = new System.Drawing.Size(130, 54);
            this.btnMemory.TabIndex = 1;
            this.btnMemory.Text = "MEMORY (OFF)";
            this.btnMemory.UseVisualStyleBackColor = true;
            this.btnMemory.Click += new System.EventHandler(this.btnMemory_Click);
            // 
            // btnDisk
            // 
            this.btnDisk.Font = new System.Drawing.Font("굴림", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
            this.btnDisk.Location = new System.Drawing.Point(3, 379);
            this.btnDisk.Name = "btnDisk";
            this.btnDisk.Size = new System.Drawing.Size(130, 54);
            this.btnDisk.TabIndex = 2;
            this.btnDisk.Text = "DISK (OFF)";
            this.btnDisk.UseVisualStyleBackColor = true;
            this.btnDisk.Click += new System.EventHandler(this.btnDisk_Click);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(800, 450);
            this.Controls.Add(this.tableLayoutPanel1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Main_FormClosing);
            this.Load += new System.EventHandler(this.Main_Load);
            this.tableLayoutPanel1.ResumeLayout(false);
            this.panel2.ResumeLayout(false);
            this.panel1.ResumeLayout(false);
            ((System.ComponentModel.ISupportInitialize)(this.sysChart)).EndInit();
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
        private System.Windows.Forms.Panel panel2;
        private System.Windows.Forms.Panel panel1;
        private System.Windows.Forms.DataVisualization.Charting.Chart sysChart;
        private System.Windows.Forms.Button btnDisk;
        private System.Windows.Forms.Button btnMemory;
        private System.Windows.Forms.Button btnCpu;
    }
}
  • program.cs
using Akka.Actor;
using System;
using System.Windows.Forms;

namespace Akka_Test
{
    static class Program
    {
        /// <summary>
        /// ActorSystem we'll be using to publish data to charts
        /// and subscribe from performance counters
        /// </summary>
        public static ActorSystem ChartActors;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            ChartActors = ActorSystem.Create("ChartActors");
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
  • 실행 결과

728x90

이 글을 공유하기

댓글

Designed by JB FACTORY