[C#] Akka 중급 2-2 | 더 나은 메시지 처리를 위해 'ReceiveActor' 사용
- C#/Akka(Actor)
- 2022. 6. 15. 21:15
참조
소개
- Unit1 에서는 UntypedActor를 사용하여 첫 번째 액터를 빌드하고 몇 가지 간단한 메시지 유형을 처리하는 방법을 배웠습니다.
- 이번 레슨에서는 ReceiveActor를 사용하여 Akka.NET에서 보다 정교한 유형의 패턴 일치 및 메시지 처리를 쉽게하는 방법을 보여줄 것입니다.
Key Concepts / Background
패턴매칭(Pattern matching)
- Akka.NET의 액터는 .NET 타입이나 값에 따라 메시지를 선택적으로 처리할 수 있는 패턴 일치의 개념에 크게 의존합니다.
- 첫 번째 모듈에서는 UntypedActor를 사용하여 다음과 같은 코드 블록을 사용하여 메시지를 처리하고 수신하는 방법을 배웠습니다.
protected override void OnReceive(object message)
{
if(message is Foo)
{
var foo = message as Foo;
//do something with foo
}
else if(message is Bar)
{
var bar = message as Bar;
//do something with bar
}
else
{
//couldn't match this message
Unhandled(message);
}
}
- Akka.NET 에서 이 패턴 일치 방법은 단순 일치에 적합하지만 일치 요구가 더 복잡하다면 어떻게 해야 할까요?
- 지금까지 살펴본 UntypedActor로 이러한 사용 사례를 어떻게 처리할지 고려하십시오.
- message가 string 이고 "AkkaDotNet" 으로 시작하는 경우 또는
- message가 Foo 타입이고 Foo.Count가 4보다 작고 Foo.Max가 10보다 큰 경우
protected override void OnReceive(object message)
{
if(message is string && message.AsInstanceOf<string>().BeginsWith("AkkaDotNet"))
{
var str = message as string;
}
else if(message is Foo && message.AsInstanceOf<Foo>.Count < 4 &&
message.AsInstanceOf<Foo>.Count > 10)
{
var foo = message as Foo;
}
else
{
Unhandled(message);
}
}
- UntypedActor를 이용하여 패턴매칭을 하니까 상당히 소스코드가 복잡해 집니다.
- 그러나, ReceiveActor를 이용하면 보다 간단하게 코드 구현이 가능합니다.
public class FooActor : ReceiveActor
{
public FooActor
{
Receive<string>(s => s.StartWith("AkkaDotNet"), s =>
{
//handle string
});
Receive<Foo>(foo => foo.Count < 4 && foo.Max > 10, foo => {
//handle foo
});
}
}
ReceiveActor의 비밀 소스
- 패턴 일치 코드를 모두 정리하는 비밀 소스는 Receive<T> 핸들러입니다.
// ReceiveActor를 파워풀하게 만드는 이유입니다.
Receive<T>(Predicate<T>, Action<T>);
- ReceiveActor를 사용하면 액터에 맞는 강력한 타입의 컴파일 타임 패턴 레이어를 쉽게 추가 할 수 있습니다.
- 타입에 따라 메시지를 쉽게 일치시킨 다음, 액터가 특정 메시지를 처리 할 수 있는지 여부를 결정할 때 추가 검사 또는 유효성 검사를 수행하기 위해 유형화된 술어를 사용할 수 있습니다.
다른 종류의 Receive<T>
핸들러가 있나요?
1. Receive<T>(Action<T> handler)
- 메시지가 'T' 타입인 경우에만 메시지 핸들러를 실행합니다.
2. Receive<T>(Predicate<T> pred, Action<T> handler)
- 이것은 메시지가 T 유형이고 predicate 함수가 T 인스턴스에 대해 true를 반환하는 경우에만 메시지 핸들러를 실행합니다.
3. Receive<T>(Action<T> handler, Predicate<T> pred)
- 이전과 같습니다.
4. Receive(Type type, Action<object> handler)
- 이것은 이전의 typed + predicate 메시지 핸들러의 구체적인 버전입니다.(더 이상 일반이 아님)
5. Receive(Type type, Action<object> handler, Predicate<object> pred)
- 이전과 같습니다.
6. ReceiveAny()
- 이것은 모든 object 인스턴스를 받아들이는 catch-all 핸들러입니다.
- 일반적으로 이 기능은 이전 Receive() 처리기에서 처리되지 않은 메시지를 처리하는 데 사용합니다.
Receive<T>
핸들러를 선언하는 순서가 중요합니다.
겹치는 메시지 유형을 처리해야하는 경우 어떻게 됩니까?
아래 메시지를 고려하십시오. 동일한 하위 문자열로 시작하지만 다르게 처리해야 한다고 가정합니다.
- AkkaDotNetSuccess 로 시작하는 string 메시지, 그리고
- AkkaDotNet으로 시작하는 string 메시지
ReceiveActor가 이렇게 작성되면 어떻게 될까요?
public class StringActor : ReceiveActor
{
public StringActor()
{
Receive<string>(s => s.StartWith("AkkaDotNet"), s => {
//handle string
});
Receive<string>(s => s.StartWith("AkkaDotNetSuccess"), s => {
//handle string
});
}
}
- 이 경우에 일어나는 일은 두 번째 핸들러(for s.StartWith("AkkaDotNetSuccess")) 가 호출되지 않는다는 것입니다.
Receive<T>
핸들러의 순서가 중요합니다.- 이는 ReceiveActor가 최상위 일치 핸들러가 아닌 첫 번째 일치 핸들러를 사용하여 메시지를 처리하고 선언 된 순서대로 각 메시지에 대한 핸들러를 평가하기 떄문입니다.
- 그렇다면 "AkkaDotNetSuccess" 로 시작하는 문자열에 대한 핸들러가 트리거되지 않는 위의 문제를 어떻게 해결합니까?
- 간단합니다. 더 구체적인 핸들러가 먼저 나오도록 하면 이 문제를 해결 할 수 있습니다.
public class StringActor : ReceiveActor
{
public StringActor()
{
//Now works as expected
Receive<string>(s => s.StartWith("AkkaDotNetSuccess"), s => {
//handle string
});
Receive<string>(s => s.StartWith("AkkaDotNet"), s =>
{
//handle string
});
}
}
ReceiveActor 에서 메시지 핸들러는 어디에서 정의합니까?
- ReceiveActor에는 OnReceive() 메서드가 없습니다.
- 대신, ReceiveActor 생성자 또는 해당 생성자가 호출 한 메서드에서 직접 Receive 메시지 핸들러를 연결해야 합니다.
- 이것을 알면 ReceiveActor를 사용하여 작업할 수 있습니다.
실습
- ChartingActor.cs
using Akka.Actor;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms.DataVisualization.Charting;
namespace Akka_Test.Actors
{
public class ChartingActor : ReceiveActor
{
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;
}
}
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));
}
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);
}
}
private void HandleInitialize(InitializeChart ic)
{
if(ic.InitialSeries != null)
{
_seriesIndex = ic.InitialSeries;
}
_chart.Series.Clear();
if(_seriesIndex.Any())
{
foreach(var series in _seriesIndex)
{
series.Value.Name = series.Key;
_chart.Series.Add(series.Value);
}
}
}
}
}
- 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
{
/// <summary>
/// Helper class for creating random data for chart plots
/// </summary>
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
using Akka.Actor;
using Akka.Util.Internal;
using Akka_Test.Actors;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
namespace Akka_Test
{
public partial class Form1 : Form
{
private IActorRef _chartActor;
private readonly AtomicCounter _seriesCounter = new AtomicCounter(1);
public Form1()
{
InitializeComponent();
}
private void Main_Load(object sender, System.EventArgs e)
{
_chartActor = Program.ChartActors.ActorOf(Props.Create(() => new ChartingActor(sysChart)), "charting");
var series = ChartDataHelper.RandomSeries("FakeSeries" + _seriesCounter.GetAndIncrement());
_chartActor.Tell(new ChartingActor.InitializeChart(new Dictionary<string, Series>()
{
{series.Name, series}
}));
}
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 uiBtn_AddSeries_Click(object sender, System.EventArgs e)
{
var series = ChartDataHelper.RandomSeries($"FakeSeries {_seriesCounter.GetAndIncrement()}");
_chartActor.Tell(new ChartingActor.AddSeries(series));
}
}
}
- 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.panel1 = new System.Windows.Forms.Panel();
this.panel2 = new System.Windows.Forms.Panel();
this.sysChart = new System.Windows.Forms.DataVisualization.Charting.Chart();
this.uiBtn_AddSeries = new System.Windows.Forms.Button();
this.tableLayoutPanel1.SuspendLayout();
this.panel1.SuspendLayout();
this.panel2.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;
//
// 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;
//
// panel2
//
this.panel2.Controls.Add(this.uiBtn_AddSeries);
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;
//
// 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";
//
// uiBtn_AddSeries
//
this.uiBtn_AddSeries.BackColor = System.Drawing.Color.White;
this.uiBtn_AddSeries.Font = new System.Drawing.Font("굴림", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.uiBtn_AddSeries.Location = new System.Drawing.Point(20, 9);
this.uiBtn_AddSeries.Name = "uiBtn_AddSeries";
this.uiBtn_AddSeries.Size = new System.Drawing.Size(104, 45);
this.uiBtn_AddSeries.TabIndex = 0;
this.uiBtn_AddSeries.Text = "Add Series";
this.uiBtn_AddSeries.UseVisualStyleBackColor = false;
this.uiBtn_AddSeries.Click += new System.EventHandler(this.uiBtn_AddSeries_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.panel1.ResumeLayout(false);
this.panel2.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 uiBtn_AddSeries;
}
}
- 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-4 | 'BecomeStacked' 와 'UnbecomeStacked' 를 사용하여 런타임에 액터 동작 전환 (0) | 2022.06.16 |
---|---|
[C#] Akka 중급 2-3 | 'Scheduler' 를 사용한 지연 메시지 보내기 (0) | 2022.06.15 |
[Akka.NET] Akka.NET Helper 정의 (0) | 2022.01.17 |
[Akka.NET]Akka.Remote 란? (0) | 2022.01.17 |
[Akka.NET] 액터 메시지 받기 (0) | 2022.01.15 |
이 글을 공유하기