[C#] Akka 중급 2-3 | 'Scheduler' 를 사용한 지연 메시지 보내기
- C#/Akka(Actor)
- 2022. 6. 15. 22:17
참조
소개
- 스케줄러 사용방법에 대해 학습합니다.
- 액터를 이용한 게시-구독 패턴 구현 방법에 대해 학습합니다.
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();
- 이것은 이전 예제에 대한 좀 더 간결한 대안이며, 이 부트캠프에서는 사용하지 않더라도 앞으로 사용하는 것이 좋습니다.
예약된 메시지의 타이밍은 얼마나 정확한가요?
- 예약된 메시지는 우리가 접한 모든 사용 사례에 대해 충분히 정확합니다.
- 그렇긴 하지만, 우리가 알고 있는 부정확한 상황이 두 가지가 있습니다.
- 예약 된 메시지는 CLR 스레드풀에 예약되고 내부적으로 Task.Delay를 사용합니다. CLR 스레드풀에 높은 로드가 있는 경우 작업이 계획보다 조금 늦게 완료될 수 있습니다. 작업이 예상 한 밀리 초에 정확히 실행된다는 보장은 없습니다.
- 스케줄링 요구 사항이 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
'C# > Akka(Actor)' 카테고리의 다른 글
[C#] Akka 중급 2-5 | 'Stash' 를 사용하여 메시지 처리 지연 (0) | 2022.06.16 |
---|---|
[C#] Akka 중급 2-4 | 'BecomeStacked' 와 'UnbecomeStacked' 를 사용하여 런타임에 액터 동작 전환 (0) | 2022.06.16 |
[C#] Akka 중급 2-2 | 더 나은 메시지 처리를 위해 'ReceiveActor' 사용 (0) | 2022.06.15 |
[Akka.NET] Akka.NET Helper 정의 (0) | 2022.01.17 |
[Akka.NET]Akka.Remote 란? (0) | 2022.01.17 |
이 글을 공유하기