[C#] Akka 중급 2-1 | HOCON Configuration을 사용하여 Akka.NET 구성
- 카테고리 없음
- 2022. 6. 15. 21:07
참조
소개
- Unit2 에서 대부분의 시간을 차트의 모든 데이터를 실제로 플로팅하는 역할을 담당하는 액터인 ChartingActor와 함께 작업할 것입니다.
Key Concepts / Background
Dispatcher 란?
- Dispatcher는 액터의 Mailbox에서 액터 인스턴스 자체로 메시지를 푸시하는 스택의 일종입니다.
- 즉, Dispatcher는 액터의 OnReceive() 메서드로 메시지를 푸시하는 것입니다.
- 주어진 Dispatcher를 공유하는 모든 액터는 병렬 실행을 위해 Dispatcher의 스레드도 공유합니다.
- Akka.NET의 기본 디스패처는 ThreadPoolDispatcher 입니다.
- 짐작할 수 있듯이, 이 디스패처는 CLR ThreadPool을 기반으로 모든 액터를 실행합니다.
어떤 종류의 Dispatcher 가 있나요?
- 액터와 함께 사용할 수 있는 여러 유형의 Dispatcher 가 있습니다.
SingleThreadDispatcher
- 이 Dispatcher는 단일 스레드에서 여러 액터를 실행합니다.
ThreadPoolDispatcher(default)
- 이 Dispatcher는 최대 동시성을 위해 CLR ThreadPool 위에서 액터를 실행합니다.
SynchronizedDispatcher
- 이 Dispatcher는 모든 액터 메시지가 호출자(Sender)와 동일한 동기화 컨텍스트에서 처리되도록 예약합니다.
- 99%의 경우 여기에서 클라이언트 애플리케이션과 같이 UI 스레드에서 액세스해야 하는 액터를 실행합니다.
- SynchronizedDispatcher는 현재 SynchronizationContext를 사용하여 실행을 예약합니다.
Note: 일반적으로 SynchronizedDispatcher에서 실행되는 액터는 많은 작업을 수행하지 않아야 합니다. 다른 풀에서 실행중인 액터가 수행 할 수 있는 추가 작업을 수행하지 마십시오.
- 이 레슨에서는 SynchronizedDispatcher 를 사용하여 ChartingActor가 WinForms 애플리케이션의 UI 스레드에서 실행되도록 할 것입니다.
- 이렇게하면 ChartingActor는 크로스 스레드 마샬링 없이 원하는 모든 UI 요소를 업데이트 할 수 있습니다.
- 액터의 Dispatcher 가 자동으로 처리할 수 있습니다.
ForkJoinDispatcher
- 이 Dispatcher는 조정 가능한 동시성을 위해 전용 스레드 그룹에서 액터를 실행합니다.
- 실행을 위해 전용 스레드가 필요한 액터(격리 보장이 필요한)를 위한 것입니다.
- 이것은 주로 System 액터가 사용하므로 많이 건드리지 않을 것입니다.
UI 스레드에서 액터를 실행하는 것이 나쁜 생각인가요?
- UI 스레드에서 액터를 실행하는 것은 해당 액터가 디스크 또는 네트워크 I/O 와 같은 장기 실행 작업을 수행하지 않는 한 괜찮습니다.
- 사실 UI 스레드에서 액터를 실행하는 것은 UI 이벤트 및 업데이트를 처리하는데 현명한 작업입니다.
- UI 스레드에서 액터를 실행하면 일반적인 동기화 문제가 모두 제거 되는데, 그렇지 않으면 다중 스레드 WPF 또는 WinForms 앱에서 해야 할 일이 있습니다.
기억하세요:Akka.NET 액터는 게으르다. 메시지를 받지 않을 때는 아무 작업도 하지 않습니다. 비활성 상태에서 리소스를 소비하지 않습니다.
Dispatcher는 깨진 차트와 어떤 관련이 있나요?
- 앞에서 본 것처럼 그래프를 수행하는 액터(ChartingActor) 가 이벤트를 UI 스레드와 동기화하지 않기 때문에 차트가 업데이트 되지 않습니다.
- 문제를 해결하기 위해 우리가 해야 할 일은 ChartingActor를 CurrentSynchronizationContextDispatcher 를 사용하도록 변경하는 것 뿐입니다. 그러면 UI 스레드에서 자동으로 실행됩니다.
- 그러나 우리는 실제 액터 코드를 건드리지 않고 이것을 하고 싶습니다. 액터 자체를 수정하지 않고 CurrentSynchronizationContextDispatcher 를 사용하도록 ChartingActor를 배포하려면 HOCON을 사용하면 됩니다.
HOCON
- Akka.NET은 HOCON이라는 구성 형식을 활용하여, 원하는 세분화 수준으로 Akka.NET 응용 프로그램을 구성할 수 있습니다.
HOCON 이란?
- HOCON(Human-Optimized Config Object Notation) 은 유연하고 확장 가능한 구성 형식입니다.
- 이를 통해 Akka.NET의 IActorRefProvider를 구현, 로깅, 네트워크 전송, 그리고 더 일반적으로 개별 액터가 배포되는 방식에서 모든 것을 구성 할 수 있습니다.
- HOCON에서 반환하는 값은 유형이 강력합니다.(int, Timespan 등을 가져올 수 있습니다.)
HOCON 으로 무엇을 할 수 있나요?
- HOCON은 App.config와 Web.config의 읽기 어려운 XML 내에 쉽게 읽을 수 있는 구성을 포함할 수 있습니다.
- HOCON은 섹션 경로 구성을 쿼리할 수 있으며 해당 섹션은 애플리케이션 내에서 사용할 수 있는 강력한 형식과 구문 분석된 값을 노출합니다.
- HOCON은 구성 섹션을 중첩하거나 아니면 연결하여 세분화 계층을 생성하고 의미상 네임스페이스 구성을 제공 할 수 있습니다.
HOCON은 일반적으로 어디에 사용되나요?
- HOCON은 일반적으로 로깅 설정을 조정하고, 특수 모듈을 활성화 하거나, 이 레슨에서 ChartingActor에 대한 Dispatcher와 같은 배포를 구성하는데 사용됩니다.
- 예를 들어 HOCON으로 ActorSystem을 구성 해 보겠습니다.
var config = ConfigurationFactory.ParseString(@"
akka.remote.helios.tcp {
transport-class = ""Akka.Remote.Transport.HeliosTcpTransport, Akka.Remote""
transport-protocol = tcp
port = 8091
hostname = ""127.0.0.1""
}");
var system = ActorSystem.Create("MyActorSystem", config);
- 예제에서 볼 수 있듯이, ConfigurationFactory.ParseString 메서드를 사용하여 string 에서 HOCON Config 객체를 구문 분석 할 수 있습니다.
- 일단 Config 객체가 있으면 ActorSystem.Create 메서드 내의 ActorSystem에 이를 전달할 수 있습니다.
NOTE: 이 예에서 우리는 Unit2 에서 다루는 개념을 훨씬 뛰어 넘는 개념인 Akka.Remote 와 함께 사용할 특정 네트워크 전송을 구성했습니다. 지금은 세부 사항에 대해 걱정하지 마십시오.
배포(Deployment)? 그건 뭔가요?
- 배포(Deployment) 는 모호한 개념이지만 HOCON과 밀접한 관련이 있습니다.
- 액터는 인스턴스화되어 ActorSystem 어딘가에 서비스될 때 배포가 됩니다.
- 액터가 ActorSystem 내에서 인스턴스화되면 로컬 프로세스 내부 또는 다른 프로세스의 두 위치 중 하나에 배치 될 수 있습니다.
- 액터가 ActorSystem에 의해 배포되면 다양한 구성 설정이 있습니다.
- 이 설정은 액터에 대한 광범위한 동작 옵션을 제어합니다. 예를 들면 다음과 같습니다.
- 이 액터가 라우터가 될까요? 어떤 Dispatcher를 사용합니까? 어떤 유형의 Mailbox가 있습니까?
- 모든 옵션의 의미를 살펴 보지는 않았지만 지금 알아야 할 핵심 사항은 액터를 서비스에 배포하기 위해 ActorSystem이 사용하는 설정을 HOCON 내에서 설정할 수 있다는 것입니다.
- 이것은 실제로 액터 코드 자체를 건드리지 않고도 액터의 동작을 극적으로 변경할 수 있음을 의미합니다.
HOCON은 App.config와 Web.config 내에서 사용할 수 있습니다.
NOTE: App.config와 Web.config는 .NET Core에서 더이상 사용되지 않는다는 것을 알고 있습니다. 독립 실행형 HOCON 3.0이 출시되면 HOCON 구성을 선언하는 새로운 방법으로 이 샘플을 업데이트 할 것입니다.
- string 에서 HOCON을 구문 분석하는 것은 작은 구성 섹션에 편리하지만 App.config 와 Web.config 에 대한 구성 변환 및 기타 모든 유용한 도구를 활용하려면 어떻게 해야 합니까? System.Configuration 네임스페이스에 있습니까?
- 결과적으로 이러한 구성 파일 내에서도 HOCON을 사용할 수 있습니다.
- 다음은 App.config 내에서 HOCON을 사용하는 예제입니다.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<configSections>
<section name="akka"
type="Akka.Configuration.Hocon.AkkaConfigurationSection, Akka"/>
</configSections>
<akka>
<hocon>
<![CDATA[
akka {
# here we are configuring log levels
log-config-on-start = off
stdout-loglevel = INFO
loglevel = ERROR
# this config section will be referenced as akka.actor
actor {
provider = "Akka.Remote.RemoteActorRefProvider, Akka.Remote"
debug {
receive = on
autoreceive = on
lifecycle = on
event-stream = on
unhandled = on
}
}
# here we're configuring the Akka.Remote module
remote {
helios.tcp {
transport-class =
"Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote"
#applied-adapters = []
transport-protocol = tcp
port = 8091
hostname = "127.0.0.1"
}
log-remote-lifecycle-events = INFO
}
]]>
</hocon>
</akka>
</configuration>
HOCON Configuration은 폴백을 지원합니다.
- Unit2 에서 명시적으로 활용하는 개념은 아니지만 많은 프로덕션 사용 사례에서 유용하게 사용되는 Config 클래스의 강력한 특성입니다.
- HOCON은 "대체(fallback)" 구성 개념을 지원합니다. 이 개념을 시각적으로 설명하는 것이 가장 쉽습니다.
- 위의 다이어그램과 같은 것을 만들려면 다음과 같은 구문을 사용하여 세 개의 폴백이 연결된 Config 객체를 만들어야 합니다.
var f0 = configurationFactory.ParseString("a = bar");
var f1 = configurationFactory.ParseString("b = biz");
var f2 = configurationFactory.ParseString("c = baz");
var f3 = configurationFactory.ParseString("a = foo");
var yourConfig = f0.WithFallback(f1)
.WithFallback(f2)
.WithFallback(f3);
- 키가 "a" 인 HOCON 개체의 값을 요청하는 경우 다음 코드를 사용합니다.
var a = yourConfig.GetString("a");
- 그러면 내부 HOCON 엔진은 키 a 에 대한 정의를 포함하는 첫 번째 HOCON 파일과 일치합니다.
- 이 경우 f0은 "bar" 값을 반환합니다.
"foo"가 "a" 의 값으로 반환되지 않은 이유는 무엇인가요?
- HOCON은 Config 체인에서 일치하는 항목이 이전에 발견되지 않은 경우에만 대체 config 객체를 검색하기 때문입니다.
- 최상위 Config 객체에 a와 일치하는 항목이 있으면 대체 항목이 검색되지 않습니다.
- 이 경우 f0 에서 a에 대한 일치가 발견되었으므로 f3의 a = foo 에는 도달하지 않았습니다.
HOCON 키 미스가 발생하면 어떻게 되나요?
- c가 f0 또는 f1 에 정의되어 있지 않은 경우 다음 코드를 실행하면 어떻게 되나요?
var c = yourConfig.GetString("c");
- 이 경우 yourConfig는 f2 까지 두 번 풀백되고 c의 키 값으로 "baz"를 반환합니다.
- 이제 HOCON을 이해했으니, 이것을 사용하여 ChartingActor의 Dispatcher를 수정해 보겠습니다.
실습
- 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 : UntypedActor
{
#region Messages
public class InitializeChart
{
public InitializeChart(Dictionary<string, Series> initialSeries)
{
InitialSeries = initialSeries;
}
public Dictionary<string, Series> InitialSeries { get; private set; }
}
#endregion
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;
}
protected override void OnReceive(object message)
{
if (message is InitializeChart)
{
var ic = message as InitializeChart;
HandleInitialize(ic);
}
}
#region Individual Message Type Handlers
private void HandleInitialize(InitializeChart ic)
{
if (ic.InitialSeries != null)
{
//swap the two series out
_seriesIndex = ic.InitialSeries;
}
//delete any existing series
_chart.Series.Clear();
//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);
}
}
}
#endregion
}
}
* **App.config**
```xml
<?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;
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();
}
}
}
- Form1.Designer.cs
namespace Akka_Test
{
partial class Form1
{
///
/// 필수 디자이너 변수입니다.
///
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 chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend();
System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
this.panel1 = new System.Windows.Forms.Panel();
this.sysChart = new System.Windows.Forms.DataVisualization.Charting.Chart();
this.panel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.sysChart)).BeginInit();
this.SuspendLayout();
//
// panel1
//
this.panel1.Controls.Add(this.sysChart);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(800, 450);
this.panel1.TabIndex = 0;
//
// sysChart
//
chartArea1.Name = "ChartArea1";
this.sysChart.ChartAreas.Add(chartArea1);
this.sysChart.Dock = System.Windows.Forms.DockStyle.Fill;
legend1.Name = "Legend1";
this.sysChart.Legends.Add(legend1);
this.sysChart.Location = new System.Drawing.Point(0, 0);
this.sysChart.Name = "sysChart";
series1.ChartArea = "ChartArea1";
series1.Legend = "Legend1";
series1.Name = "Series1";
this.sysChart.Series.Add(series1);
this.sysChart.Size = new System.Drawing.Size(800, 450);
this.sysChart.TabIndex = 1;
this.sysChart.Text = "sysChart";
//
// 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.panel1);
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.panel1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.sysChart)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.DataVisualization.Charting.Chart sysChart;
}
}
* **Program.cs**
```csharp
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());
}
}
}
- 실행 결과
이 글을 공유하기