참조
소개
- BecomeStacked와 UnbecomeStacked를 사용하여 런타임에 액터 동작 전한하는 방법에 대해서 알아봅니다.
실제 시나리오 : 인증
- Akka.NET 액터를 사용해 간단한 채팅 시스템을 구축한다고 가정해 보겠습니다.
- 특정 사용자와의 모든 통신을 담당하는 액터가 당신이 원하는 UserActor의 모습입니다.
public class UserActor : ReceiveActor
{
private readonly string _userId;
private readonly string _charRoomId;
public UserActor(string userId, string chatRoomId)
{
_userId = userId;
_charRoomId = chatRoomId;
Receive<IncomingMessage>(inc => inc.ChatRoomId == _chatRoomId,
inc => {
// print message for user
});
Receive<OutgoingMessage>(int => inc.ChatRoomId == _chatRoomId,
int => {
// send message to chatroom
});
}
}
- 아래와 같이 같은 타입의 채팅 메시지를 다르게 처리할 수 있도록 액터를 다시 작성할 수 있을까요?
- 인증 중인 사용자
- 인증된 사용자이거나
- 인증할 수 없는 사용자?
- 간단합니다. 액터의 전환 가능 동작(switchable behavior) 을 이용해 할 수 있습니다.
전환 가능 동작(switchable behavior)이 무엇인가요?
- Actor Model 에서 액터의 핵심 속성 중 하나는 액터가 처리하는 메시지 간에 행동을 변경할 수 있다는 것입니다.
- 이 기능을 사용하면, 유한 상태 머신(Finite-state machine) 빌드나 액터가 수신한 메시지에 따라 메시지를 처리하는 방식을 변경하는 것과 같은 모든 종류의 작업을 수행할 수 있습니다.
- 전환 가능 동작은 진정한 액터 시스템의 가장 강력하고 기본적인 기능 중 하나입니다.
- 액터의 재사용을 가능하게 하는 주요 기능 중 하나이며, 매우 작은 코드 풋프린트로 엄청난 양의 작업을 수행할 수 있도록 도와줍니다.
동작 스택(Behavior Stack)
- Akka.NET 액터는 "동작스택" 이라는 개념을 가지고 있습니다.
- 동작 스택의 맨 위에 있는 방법은 액터의 현재 동작을 정의합니다.
- 현재 동작은 Authenticating() 입니다.
Become과 BecomeStacked 를 사용하여 새로운 동작을 채택
- BecomeStacked를 호출할 때마다 ReceiveActor에게 새로운 동작을 스택에 푸시하도록 지시합니다.
- 이 새로운 동작은 액터에게 전달된 메시지를 처리하는 데 사용할 Receive 메서드를 지정합니다.
- 예제 액터가 BecomeStacked를 통해 Authenticated가 될 때 동작 스택에 일어나는 일입니다.
NOTE : Become은 스택에서 이전 동작을 삭제하므로 스택에는 한 번에 하나 이상의 동작이 포함되지 않습니다.
동작을 스택에 푸시하려면 BecomeStacked를 사용하고 이전 동작으로 되돌리려면 UnbecomeStacked를 사용합니다. 사용자는 대부분 Become만 사용하면 됩니다.
UnbecomeStacked를 사용하여 이전 동작으로 되돌리기
- 액터가 동작 스택의 이전 동작으로 되돌아 가도록 하려면 UnbecomeStacked를 호출하기만 하면 됩니다.
- UnbecomeStacked를 호출할 때마다 스택에서 현재 동작을 꺼내서 이전 동작으로 대체합니다.
- 다시 말하지만 이 새로운 동작은 들어오는 메시지를 처리하는 데 사용되는 Receive 메서드를 말합니다.
- 다음은 예제 액터 UnbecomeStacked 가 작동할 때 동작 스택에 일어나는 일입니다.
동작을 변경하는 API는 무엇입니까?
동작을 변경하는 API는 정말 간답합니다.
- Become - 현재 수신 루프를 지정된 루프로 바꿉ㄴ디ㅏ. 동작 스택을 제거합니다.
- BecomeStacked - 지정된 메서드를 동작 스택의 맨 위에 추가하고 그 아래의 이전 메서드를 유지합니다.
- UnBecomeStacked - 스택에서 이전에 수신한 메서드로 되돌립니다. (BecomeStacked 에서만 작동)
BecomeStacked 는 이전 동작을 보존하므로 UnbecomeStacked를 호출하여 이전 동작으로 돌아갈 수 있다는 것이 차이점입니다.
다른 무엇보다 당신의 필요에 달려 있습니다.
필요한 만큼 BecomeStacked를 호출할 수 있으며, BecomeStacked를 호출한 횟수만큼 UnbecomeStacked를 호출할 수 있습니다.
현재 동작이 스택의 유일한 동작인 경우 UnbecomeStacked에 대한 추가 호출은 아무 작업도 수행하지 않습니다.
액터가 행동을 바꾸는 것이 문제가 되지 않나요?
- 아니요. 실제로 안전하며 ActorSystem에 엄청난 유연성과 코드 재사용을 제공하는 기능입니다.
- 다음은 전환 가능 동작에 대한 몇 가지 일반적이 질문입니다.
새로운 동작은 언제 적용되나요?
- Akka.NET 액터는 한 번에 하나의 메시지만 처리하므로 액터 메시지 처리 동작을 안전하게 전환할 수 있습니다.
- 새 메시지 처리 동작은 다음 메시지가 도착할 때까지 적용되지 않습니다.
Become이 동작 스택을 날려 버리는 것이 나쁘지 않나요?
- 지금까지 가장 일반적으로 사용되는 방식입니다.
- 한 동작에서 다른 동작으로 명시적으로 전환하는 것이 동작 전황에 사용되는 가장 일반적인 방식입니다.
- 간단하고 명시적인 스위치를 사용하면 코드를 훨씬 쉽게 읽고 추론할 수 있습니다.
- 실제로 동작 스택을 활용해야 하는 경우 - 단순하고, 명시적인 become 이 상황에 맞지 않는 경우, 동작스택을 사용할 수 있습니다.
- 이 레슨에서는 BecomeStacked와 UnbecomeStacked를 사용하여 시연합니다. 보통 우리는 그냥 Becom을 사용합니다.
동작 스택의 깊이는 얼마나 되나요?
- 스택은 정말 깊을 수 있지만, 무제한은 아닙니다.
- 또한 액터가 다시 시작될 때마다 동작 스택이 지워지고 사용자가 코드화한 초기 동작부터 액터가 시작됩니다.
UnbecomeStacked를 호출하고 동작 스택에 아무것도 남지 않으면 어떻게 되나요?
- UnbecomeStacked는 안전한 방법이며 현재 동작이 스택의 유일한 동작인 경우 아무 작업도 수행하지 않습니다.
실제 사례로 돌아가기
이제 전환 가능 동작을 이해했으므로, 실제 시나리오로 돌아가서 어떻게 사용되는지 살펴보겠습니다.
채팅 시스템 액터에 인증을 추가해야 합니다.
따라서 다음과 같은 경우 채팅 메시지를 다르게 처리하도록 이 액터를 다시 작성할 수 있나요?
- 인증 중인 사용자
- 인증된 사용자이거나
- 인증할 수 없는 사용자?
다음은 기본 인증을 처리하기 위해 UserActor에서 전환 가능 메시지 동작을 구현할 수 있는 한가지 방법입니다.
public class UserActor : ReceiveActor
{
private readonly string _userId;
private readonly string _chatRoomId;
public UserActor(string userId, string chatRoomId)
{
_userId = userId;
_chatRoomID = chatRoomId;
// start with the Authenticating behavior
Authenticating();
}
protected override void PreStart()
{
//start the authentication process for this user
Context.ActorSelection("/user/authenticator/");
.Tell(new AuthenticatePlease(_userId));
}
private void Authenticating()
{
Receive<AuthenticationSuccess>(auth => {
Become(Authenticated); //switch behavior to Authenticated
});
Receive<AuthenticationFailure>(auth => {
Become(Unauthenticated); //switch behavior to Unauthenticated
});
Receive<IncomingMessage>(inc => inc.ChatRoomId == _chatRoomId,
inc => {
// can't accept message yet - not auth'd
});
Receive<OutgoingMessage>(inc => inc.ChatRoomId == _chatRoomId,
inc => {
// can't send message yet - not auth'd
});
};
private void Unauthenticated()
{
//switch to Authenticating
Receive<RetryAuthentication>(retry => Become(Authenticating));
Receive<IncomingMessage>(inc => inc.ChatRoomId == _chatRoomId,
inc => {
//have to reject message - auth failed.
});
Receive<OutgoingMessage>(inc => inc.ChatRoomId == _chatRoomId,
inc => {
//have to reject message - auth failed.
})
}
private void Authenticated()
{
Receive<IncomingMessage>(inc => inc.ChatRoomId == _chatRoomId,
inc => {
// print message for user
});
Receive<OutgoingMessage>(inc => inc.ChatRoomId == _chatRoomId,
inc => {
// send message to chatroom
});
}
}
- 먼저 ReceiveActor 에 정의된
Receive<T>
핸들러를 가져와서 세가지 별도의 메서드로 옮겼습니다. - 이러한 각 메서드는 메시지를 처리하는 방법을 제어하는 상태를 나타냅니다.
- Authenticating() : 이 동작은 사용자가 인증을 시도 할 때 메시지를 처리하는 데 사용됩니다.
- Authenticated() : 이 동작은 인증 작업이 성공할 때 메시지를 처리하는 데 사용됩니다.
- Unauthenticated() : 이 동작은 인증 작업이 실패 할 때 메시지를 처리하는데 사용됩니다.
- 생성자에서 Authenticating()을 호출했으므로, 액터는 Authenticating() 상태에서 시작했습니다.
- 즉, Authenticating() 메서드에 정의된
Receive<T>
핸들러만 메시지 처리에 사용됩니다. - 그러나, AuthenticationSuccess 또는 AuthenticationFailure 유형의 메시지를 수신하면 Become 메서드를 사용하여 동작을 각각 Authenticated 또는 Unauthenticated로 전환합니다.
UntypedActor 에서 동작을 전환 할 수 있나요?
- UntypedActor 에서 동작을 전환하려면 직접 호출하는 대신 ActorContext를 통해 BecomeStacked 와 UnbecomeStacked에 엑세스 해야 합니다.
- UntypedActor 내부의 API 호출입니다.
- Context.Become - 스택의 이전 동작을 보존하지 않고 동작을 변경합니다.
- Context.BecomeStacked - 스택에 새로운 동작을 푸시하거나
- Context.UnbecomeStacked() - 현재 동작을 팝하고 이전 동작으로 전환합니다.
- Context.Become 의 첫 번째 인수는 Receive 대리자로, 다음 서명을 가진 모든 메서드입니다.
void MethodName(object someParameterName);
- 이 대리자(delegate)는 메시지를 수신하고 새 동작 상태를 나타내는 액터의 다른 메서드를 나타내는 데만 사용됩니다.
- 다음은 예입니다. (OtherBehavior 는 Receive 대리자입니다.)
public class MyActor : UntypedActor
{
protected override void OnReceive(object message)
{
if(message is SwitchMe)
{
//preserve the previous behavior on the stack
Context.BecomeStacked(OtherBehavior);
}
}
//OtherBehavior is a Receive delegate
private void OtherBehavior(object message)
{
if(message is SwitchMeBack)
{
//switch back to previous behavior on the
Context.UnbecomeStacked();
}
}
}
- 이러한 구문상의 차이를 제외하고, 동작 전환은 UntypedActor와 ReceiveActor 모두에게 정확히 동일한 방식으로 작동합니다.
실습
- 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;
using System.Windows.Forms;
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 TogglePause { }
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 readonly Button _pauseButton;
private Dictionary<string, Series> _seriesIndex;
public ChartingActor(Chart chart, Button pauseButton) : this(chart, new Dictionary<string, Series>(), pauseButton)
{
}
public ChartingActor(Chart chart, Dictionary<string, Series> seriesIndex, Button pauseButton)
{
_chart = chart;
_seriesIndex = seriesIndex;
_pauseButton = pauseButton;
Charting();
}
private void Charting()
{
Receive<InitializeChart>(ic => HandleInitialize(ic));
Receive<AddSeries>(addSeries => HandleAddSeries(addSeries));
Receive<RemoveSeries>(removeSeries => HandleRemoveSeries(removeSeries));
Receive<Metric>(metric => HandleMetrics(metric));
// new receive handler for the TogglePause message type
Receive<TogglePause>(pause =>
{
SetPauseButtonText(true);
BecomeStacked(Paused);
});
}
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 HandleMetricsPaused(Metric metric)
{
if(!string.IsNullOrEmpty(metric.Series)
&& _seriesIndex.ContainsKey(metric.Series))
{
var series = _seriesIndex[metric.Series];
//set the Y value to zero when we're paused
series.Points.AddXY(xPosCounter++, 0.0d);
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;
}
}
private void SetPauseButtonText(bool paused)
{
_pauseButton.Text = $"{(!paused ? "PAUSE ||" : "RESUME ->")}";
}
private void Paused()
{
Receive<Metric>(metric => HandleMetricsPaused(metric));
Receive<TogglePause>(pause =>
{
SetPauseButtonText(false);
UnbecomeStacked();
});
}
}
}
- 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.cs
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, btnPauseResume)), "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());
}
private void btnPauseResume_Click(object sender, System.EventArgs e)
{
_chartActor.Tell(new ChartingActor.TogglePause());
}
}
}
- 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 chartArea2 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
System.Windows.Forms.DataVisualization.Charting.Legend legend2 = new System.Windows.Forms.DataVisualization.Charting.Legend();
System.Windows.Forms.DataVisualization.Charting.Series series2 = 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.btnPauseResume = 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.btnPauseResume);
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
//
chartArea2.Name = "ChartArea1";
this.sysChart.ChartAreas.Add(chartArea2);
this.sysChart.Dock = System.Windows.Forms.DockStyle.Fill;
legend2.Name = "Legend1";
this.sysChart.Legends.Add(legend2);
this.sysChart.Location = new System.Drawing.Point(0, 0);
this.sysChart.Name = "sysChart";
series2.ChartArea = "ChartArea1";
series2.Legend = "Legend1";
series2.Name = "Series1";
this.sysChart.Series.Add(series2);
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);
//
// btnPauseResume
//
this.btnPauseResume.Font = new System.Drawing.Font("굴림", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.btnPauseResume.Location = new System.Drawing.Point(2, 144);
this.btnPauseResume.Name = "btnPauseResume";
this.btnPauseResume.Size = new System.Drawing.Size(130, 54);
this.btnPauseResume.TabIndex = 3;
this.btnPauseResume.Text = "PAUSE ||";
this.btnPauseResume.UseVisualStyleBackColor = true;
this.btnPauseResume.Click += new System.EventHandler(this.btnPauseResume_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;
private System.Windows.Forms.Button btnPauseResume;
}
}
- 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());
}
}
}
- 실행 결과
'C# > Akka(Actor)' 카테고리의 다른 글
[C#] Akka 중급 2-5 | 'Stash' 를 사용하여 메시지 처리 지연 (0) | 2022.06.16 |
---|---|
[C#] Akka 중급 2-3 | 'Scheduler' 를 사용한 지연 메시지 보내기 (0) | 2022.06.15 |
[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 |
이 글을 공유하기