[C#] CPU Usage 사용량

C# 2026. 4. 21. 12:36 |
반응형

Performance Counter를 사용해 여러 가지 정보를 가져올 수 있다.

 

using System;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 퍼포먼스 카운터의 카테고리를 가져와서 각 카테고리의 이름, 유형, 도움말을 출력하고,
            // 각 카테고리에 속한 인스턴스와 카운터를 출력하는 코드
            // 리스트가 너무 길어질 수 있으므로, "Processor Information" 카테고리에 속한 카운터만 출력하도록 필터링
            PerformanceCounterCategory[] categories = PerformanceCounterCategory.GetCategories();
            foreach (PerformanceCounterCategory category in categories)
            {
                Console.WriteLine("Category name: {0}", category.CategoryName);
                Console.WriteLine("Category type: {0}", category.CategoryType);
                Console.WriteLine("Category help: {0}", category.CategoryHelp);
                string[] instances = category.GetInstanceNames();

                if (instances.Any())
                {
                    foreach (string instance in instances)
                    {
                        if (category.InstanceExists(instance))
                        {
                            PerformanceCounter[] countersOfCategory = category.GetCounters(instance);
                            foreach (PerformanceCounter pc in countersOfCategory)
                            {
                                if (pc.CategoryName == "Processor Information")
                                {
                                    Console.WriteLine("■ Category: {0}, ■ Counter: {1}, ■ Instance: {2}", pc.CategoryName, pc.CounterName, instance);
                                }
                            }
                        }
                    }
                }
                else
                {
                    PerformanceCounter[] countersOfCategory = category.GetCounters();
                    foreach (PerformanceCounter pc in countersOfCategory)
                    {
                        if (pc.CategoryName == "Processor Information")
                        {
                            Console.WriteLine("Category: {0}, counter: {1}", pc.CategoryName, pc.CounterName);
                        }
                    }
                }
            }
        }
    }
}

 

 

위 결과를 이용해 CPU 사용량을 측정해 보자.

 

using System;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string categoryName = "Processor Information";
            string counterName = "% Processor Utility";
            string instanceName = "_Total";
            float cpuPercent = 0.0f;

            PerformanceCounter cpuCounter = new PerformanceCounter(categoryName, counterName, instanceName);
            // categoryName: The category of the performance counter, in this case "Processor Information".
            // counterName: The specific counter to monitor, in this case "% Processor Utility".
            // instanceName: The instance of the counter, in this case "_Total" for overall CPU usage.

            // The first call to NextValue() returns 0, so we call it once before entering the loop            
            cpuCounter.NextValue();

            while (true)
            {
                Thread.Sleep(1000);
                cpuPercent = cpuCounter.NextValue();
                Console.WriteLine("CPU Usage: {0}%", cpuPercent);
            }
        }
    }
}

 

 

반응형
Posted by J-sean
:

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

반응형

LiveCharts2를 설치하고 간단한 차트를 그려보자.

 

Windows Forms App을 선택한다.

 

Windows Forms App (.NET Framework)를 선택하면 빌드 후 실행 시 에러가 발생한다.

 

 

 

LiveChartsCore.SkiaSharpView.WinForms를 설치한다.

 

Form에 버튼을 하나 배치한다.

 

using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Drawing.Geometries;
using LiveChartsCore.SkiaSharpView.Painting;
using LiveChartsCore.SkiaSharpView.VisualElements;
using LiveChartsCore.SkiaSharpView.WinForms;
using SkiaSharp;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        private bool _isChartDrawn = false;

        public Form1()
        {
            InitializeComponent();

            button1.Click += Button1_Click;
        }

        private void Button1_Click(object? sender, EventArgs e)
        {
            if (_isChartDrawn) return;
            _isChartDrawn = true;

            double[] values1 = new double[] { 2, 1, 3, 5, 3, 4, 6 };
            int[] values2 = new int[] { 4, 2, 5, 2, 4, 5, 3 };

            ISeries[] series = new ISeries[]
            {
                new LineSeries<double>
                {
                    Values = values1,
                    Fill = null,
                    GeometrySize = 20
                },
                new LineSeries<int, StarGeometry>
                {
                    Values = values2,
                    Fill = null,
                    GeometrySize = 20
                }
            };

            DrawnLabelVisual title = new DrawnLabelVisual(
                new LabelGeometry
                {
                    Text = "My chart title",
                    Paint = new SolidColorPaint(SKColor.Parse("#303030")),
                    TextSize = 25,
                    Padding = new LiveChartsCore.Drawing.Padding(15),
                    // padding은 텍스트와 라벨의 경계 사이의 간격을 지정하는 속성이다.
                    // 위 코드에서는 15로 설정되어 있어 텍스트와 라벨의 경계 사이에 15픽셀의 간격이 생긴다.
                    VerticalAlign = LiveChartsCore.Drawing.Align.Start,
                    // vertical align은 Start, Middle, End가 있다. Start는 위쪽, Middle은 가운데, End는 아래쪽에 위치한다.
                    HorizontalAlign = LiveChartsCore.Drawing.Align.Start
                    // horizontal align은 Start, Middle, End가 있다. Start는 왼쪽, Middle은 가운데, End는 오른쪽에 위치한다.
                });

            CartesianChart cartesianChart = new CartesianChart
            {
                Series = series,
                Title = title,
                Location = new System.Drawing.Point(0, 0),
                Size = new System.Drawing.Size(800, 300),
                //Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom
                // 위 코드와 같이 Anchor를 지정하면 폼의 크기가 변경될 때 차트의 크기가 자동으로 조정되는데 그려진 비율이 유지되는게 아니라
                // 그려지지 않은 부분의 사이즈가 그대로 유지되는 방식으로 조정된다.
            };

            Controls.Add(cartesianChart);
        }
    }
}

 

코드를 입력하고 빌드한다.

 

 

버튼을 클릭하면 차트가 표시된다.

 

이번엔 차트에 Zoom & Pan 모드를 설정해 보자.

using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.WinForms;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        private bool _isChartAdded = false;

        public Form1()
        {
            InitializeComponent();

            button1.Click += Button1_Click;
        }

        private void Button1_Click(object? sender, EventArgs e)
        {
            if (_isChartAdded) return;
            _isChartAdded = true;

            int[] values = Fetch();
            ISeries[] series = new ISeries[]
            {
                new LineSeries<int>
                {
                    Values = values
                }
            };

            CartesianChart cartesianChart = new CartesianChart
            {
                Series = series,
                ZoomMode = LiveChartsCore.Measure.ZoomAndPanMode.X, // X: Enables zooming and panning on the X axis.
                // 줌 및 팬 모드를 설정하여 X축에서만 작동하도록 지정한다.
                Location = new System.Drawing.Point(0, 0),
                Size = new System.Drawing.Size(800, 300),
                //Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom
            };

            Controls.Add(cartesianChart);
        }

        private static int[] Fetch()
        {
            int[] values = new int[100];
            Random r = new Random();
            int t = 0;

            for (int i = 0; i < 100; i++)
            {
                t += r.Next(-90, 100);
                values[i] = t;
            }

            return values;
        }
    }
}

 

코드를 빌드하고 실행한다.

 

왼쪽 버튼: Pan

오른쪽 버튼: Select

휠: Zoom

 

실시간 업데이트 차트를 만들어 보자.

using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;
using LiveChartsCore.SkiaSharpView.WinForms;
using SkiaSharp;
using System.Collections.ObjectModel;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        private readonly ObservableCollection<DateTimePoint> _values = new ObservableCollection<DateTimePoint>();
        private readonly object _sync = new object();
        private bool _isReading = true;
        private double[] _separators = Array.Empty<double>();
        private readonly CartesianChart _cartesianChart = new CartesianChart();
        private bool _isChartAdded = false;

        public Form1()
        {
            InitializeComponent();

            button1.Click += Button1_Click;
        }

        private void Button1_Click(object? sender, EventArgs e)
        {
            if (_isChartAdded) return; // 차트가 이미 추가된 경우, 중복 추가 방지
            _isChartAdded = true;

            ISeries[] seriesColection = new ISeries[]
            {
                new LineSeries<DateTimePoint>
                {
                    Values = _values,
                    Fill = null,
                    GeometryFill = null,
                    GeometryStroke = null
                }
            };

            Axis xAxis = new Axis
            {
                Labeler = value => Formatter(new DateTime((long)value)),
                AnimationsSpeed = TimeSpan.FromMilliseconds(0),
                SeparatorsPaint = new SolidColorPaint(SKColors.Gray),
                CustomSeparators = _separators
            };

            _cartesianChart.Series = seriesColection;
            _cartesianChart.XAxes = [xAxis];
            _cartesianChart.Location = new System.Drawing.Point(0, 0);
            _cartesianChart.Size = new System.Drawing.Size(800, 300);
            //_cartesianChart.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;

            Controls.Add(_cartesianChart);
            // 중복 추가를 방지하기 위해 폼 컨트롤에 있는지 아래와 같이 확인할 수도 있다.
            //if (!Controls.Contains(_cartesianChart))
            //    Controls.Add(_cartesianChart);

            _ = ReadData(xAxis);
            // _ = 는 비동기 메서드의 반환값(Task)을 무시하기 위한 구문이다. 반환값은 주로 비동기 작업의 완료를 나타내는
            // Task 객체이지만, 여기서는 반환값을 사용하지 않으므로 _ = 구문을 사용하여 명시적으로 무시한다.
            // 이게 없으면 컴파일러가 반환값이 사용되지 않았다고 경고한다.
        }
        private async Task ReadData(Axis xAxis) // 데이터를 비동기적으로 읽어와 차트에 업데이트하는 메서드
        {
            Random random = new Random();
            while (_isReading)
            {
                await Task.Delay(100);
                lock (_sync)
                {
                    _values.Add(new DateTimePoint(DateTime.Now, random.Next(0, 10)));
                    // DateTimePoint: 날짜와 값을 함께 저장하는 클래스
                    if (_values.Count > 250) _values.RemoveAt(0);
                    _separators = GetSeparators();
                    xAxis.CustomSeparators = _separators;
                }
            }
        }

        private static double[] GetSeparators() // 차트의 X축에 표시할 구분자(시간 간격)를 생성하는 메서드
        {
            DateTime now = DateTime.Now;
            return new double[]
            {
                now.AddSeconds(-25).Ticks,
                now.AddSeconds(-20).Ticks,
                now.AddSeconds(-15).Ticks,
                now.AddSeconds(-10).Ticks,
                now.AddSeconds(-5).Ticks,
                now.Ticks
            };
        }

        private static string Formatter(DateTime date) // X축 레이블을 포맷팅하는 메서드
        {
            double secsAgo = (DateTime.Now - date).TotalSeconds;
            return secsAgo < 1 ? "now" : $"{secsAgo:N0}s ago";
            // "N0"는 소수점 없는 숫자 형식 지정자.
        }
    }
}

 

빌드하고 실행한다.

 

 

Angular Gauge를 만들어 보자.

using LiveChartsCore.Defaults;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Extensions;
using LiveChartsCore.SkiaSharpView.Painting;
using LiveChartsCore.SkiaSharpView.VisualElements;
using LiveChartsCore.SkiaSharpView.WinForms;
using SkiaSharp;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        private readonly PieChart pieChart = new PieChart();
        private readonly Random random = new Random();
        private bool _isChartAdded = false;

        public Form1()
        {
            InitializeComponent();

            button1.Click += Button1_Click;
        }

        private void Button1_Click(object? sender, EventArgs e)
        {
            if (_isChartAdded) return; // 차트가 이미 추가된 경우, 중복 추가 방지
            _isChartAdded = true;

            double sectionsOuter = 130; // 섹션의 외부 반경 오프셋
            double sectionsWidth = 20; // 섹션의 최대 반경 너비

            NeedleVisual needle = new NeedleVisual
            {
                Value = 45 // 바늘이 가리키는 값 (pieChart MinValue와 MaxValue 사이)
            };

            pieChart.Series = GaugeGenerator.BuildAngularGaugeSections(
                    new GaugeItem(60, s => SetStyle(sectionsOuter, sectionsWidth, SKColors.LimeGreen, s)), // 첫 번째 섹션: 60% 비율, 녹색
                    new GaugeItem(30, s => SetStyle(sectionsOuter, sectionsWidth, SKColors.Gold, s)),      // 두 번째 섹션: 30% 비율, 노란색
                    new GaugeItem(10, s => SetStyle(sectionsOuter, sectionsWidth, SKColors.Crimson, s)));  // 세 번째 섹션: 10% 비율, 빨간색
            pieChart.VisualElements = [
                new AngularTicksVisual
                {
                    Labeler = value => value.ToString("N1"), // 눈금 레이블 포맷, N1은 소수점 한 자리까지 표시
                    LabelsSize = 16,
                    LabelsOuterOffset = 15,
                    OuterOffset = 65,
                    TicksLength = 20
                },
                needle
                ];
            pieChart.InitialRotation = -225; // 시작 각도 설정
            pieChart.MaxAngle = 270; // 게이지가 차지하는 각도 설정
            pieChart.MinValue = 0; // 게이지의 최소값 설정
            pieChart.MaxValue = 100; // 게이지의 최대값 설정
            pieChart.Location = new System.Drawing.Point(0, 0);
            pieChart.Size = new System.Drawing.Size(400, 400);
            // 바늘(및 차트 전체)의 애니메이션 회전 속도를 늘리거나 줄일 수 있다.
            // 기본값은 약 500밀리초. 아래 설정은 3초(3000밀리초) 동안 부드럽게 회전하도록 한다.
            pieChart.AnimationsSpeed = TimeSpan.FromMilliseconds(3000);
            // 애니메이션의 움직임(Easing) 방식을 지정할 수도 있다.
             pieChart.EasingFunction = LiveChartsCore.EasingFunctions.BounceOut;
            //Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;

            Controls.Add(pieChart);

            Button b1 = new Button
            {
                Text = "Update",
                Location = new System.Drawing.Point(400, 300),
                Size = new System.Drawing.Size(100, 50)
            };
            b1.Click += (sender, e) => needle.Value = random.Next(0, 100);
            Controls.Add(b1);
            b1.BringToFront();
        }

        private static void SetStyle(double sectionsOuter, double sectionsWidth, SKColor color, PieSeries<ObservableValue> series)
        {
            series.OuterRadiusOffset = sectionsOuter; // 섹션의 외부 반경 오프셋 설정
            series.MaxRadialColumnWidth = sectionsWidth; // 섹션의 최대 반경 너비 설정
            series.CornerRadius = 0; // 섹션의 모서리 반경 설정
            series.Fill = new SolidColorPaint(color); // 섹션의 칠하기 색상 설정
        }
    }
}

 

 

※ 참고

Toolbox - LiveChartsCore.SkiaSharpView.WinForms 툴은 사용하지 말자. 그리는 건 되지만 지울 수가 없다.

 

LiveCharts는 코드로만 작성하거나 코드로 UserControl을 만들어 Toolbox에서 사용해야 한다.

 

※ 참고

LiveCharts2 (화면 상단의 Framework를 WinForms 등 원하는 대로 선택해야 한다)

 

반응형
Posted by J-sean
:
반응형

우선 DateTime 클래스와 TimeSpan 클래스를 사용해 보자.

 

using System;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            DateTime now = DateTime.Now;
            Console.WriteLine(now);

            TimeSpan hours = TimeSpan.FromHours(3);
            Console.WriteLine(now - hours);

            TimeSpan timeSpan = new TimeSpan(3, 30, 30); // 3 hours, 30 minutes, 30 seconds
            Console.WriteLine(now - timeSpan);

            DateTime past1 = new DateTime(2026, 1, 1);
            Console.WriteLine(now - past1); // TimeSpan

            string str = "20260101";
            DateTime past2 = DateTime.ParseExact(str, "yyyyMMdd", null);
            Console.WriteLine(now - past2); // TimeSpan

            string str2 = "20260101-153030";
            DateTime past3 = DateTime.ParseExact(str2, "yyyyMMdd-HHmmss", null);
            Console.WriteLine(now - past3); // TimeSpan            

            TimeSpan diff = now - past3;
            Console.WriteLine(new TimeSpan(diff.Days, diff.Hours, diff.Minutes, diff.Seconds));
            Console.WriteLine($"{diff.Days}일 {diff.Hours}시간 {diff.Minutes}분 {diff.Seconds}초");
        }
    }
}

 

 

이번엔 DateTimePicker로 날짜와 시간을 선택하고 DateTime 클래스를 사용해 보자.

 

Form에 DateTimePicker, TextBox, Button을 적당히 배치한다.

 

using System;
using System.IO;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        string date;
        string time;
        string dateTime;
        string[] mkvFiles;
        DateTime[] mkvFilesInDateTime;

        public Form1()
        {
            InitializeComponent();

            // 현재 날짜와 시간 초기화
            date = DateTime.Now.ToString("yyyyMMdd");
            time = DateTime.Now.ToString("HHmmss");
            dateTime = date + "-" + time;

            // 이벤트 핸들러 등록
            dateTimePicker1.ValueChanged += dateTimePicker1_ValueChanged;
            dateTimePicker2.ValueChanged += dateTimePicker2_ValueChanged;

            // DateTimePicker1 설정, 이렇게 하면 날짜와 시간 모두 선택할 수 있다.
            //dateTimePicker1.Format = DateTimePickerFormat.Custom;
            //dateTimePicker1.CustomFormat = "yyyy-MM-dd HH:mm:ss";

            // DateTimePicker2 설정, 시간과 분만 선택할 수 있도록 설정한다.
            dateTimePicker2.Format = DateTimePickerFormat.Custom;
            dateTimePicker2.CustomFormat = "HH:mm"; // 시간과 분만 표시하여 초 선택을 없앰
            dateTimePicker2.ShowUpDown = true; // 시간 선택을 위한 UpDown 컨트롤 표시

            // TextBox 설정
            textBox1.Multiline = true;
            textBox1.Height = textBox1.Font.Height * 6;

            // Button 이벤트 핸들러 등록
            button1.Click += button1_Click;

            // MKV 파일 검색 및 TextBox에 출력
            GetMkvFiles();
        }

        private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
        {
            DateTime selectedDate = dateTimePicker1.Value;

            date = selectedDate.ToString("yyyyMMdd");
            dateTime = date + "-" + time;
        }

        private void dateTimePicker2_ValueChanged(object sender, EventArgs e)
        {
            DateTime selectedTime = dateTimePicker2.Value;
            // 초 선택이 없으므로 "HHmm00"으로 초를 항상 00으로 고정
            time = selectedTime.ToString("HHmm00");
            dateTime = date + "-" + time;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 버튼 클릭 시 dateTime이 비어있지 않으면 작업 수행
            if (!string.IsNullOrEmpty(dateTime))
            {
                DateTime selectedDateTime = DateTime.ParseExact(dateTime, "yyyyMMdd-HHmmss", null);
                DateTime closestDateTime = DateTime.MinValue;
                string closestFile = string.Empty;

                // mkvFilesInDateTime 배열의 각 요소를 순회하면서 selectedDateTime과 비교하여 이전 시점의 가장 가까운 파일을 찾는다.
                foreach (DateTime fileDateTime in mkvFilesInDateTime)
                {
                    if (fileDateTime <= selectedDateTime)
                    {
                        if (fileDateTime > closestDateTime)
                        {
                            closestDateTime = fileDateTime;
                            closestFile = fileDateTime.ToString("yyyyMMdd-HHmmss") + ".mkv";
                        }
                    }
                }

                if (!string.IsNullOrEmpty(closestFile))
                    MessageBox.Show("가장 가까운 파일: " + closestFile);
                else
                    MessageBox.Show("선택한 날짜와 시간보다 이전에 생성된 파일이 없습니다.");
            }
            else
            {
                MessageBox.Show("날짜와 시간을 선택해주세요.");
            }
        }

        private void GetMkvFiles()
        {
            // 검색할 대상 폴더 경로 지정
            string directoryPath = @".\Video";
            // 문자열 앞에 @를 붙이면 이스케이프 시퀀스를 무시하고 문자열을 그대로 사용할 수 있다.
            // 1. 이스케이프 시퀀스(\) 무시 (파일 경로에 유용)
            //  보통 C# 문자열 안에서 백슬래시(\)는 이스케이프 문자로 쓰이기 때문에(\n, \t 등), 파일 경로를 적을 때 백슬래시를
            //  두 번씩 적어줘야 한다. 하지만 @를 붙이면 백슬래시를 있는 그대로 인식한다.
            // 2. 여러 줄(Multi-line) 문자열 작성
            //  @를 사용하면 \n이나 Environment.NewLine을 쓰지 않고도 엔터를 쳐서 여러 줄의 문자열을 그대로 작성할 수 있다.

            //  경로가 존재하는지 확인
            if (Directory.Exists(directoryPath))
            {
                // 지정된 폴더에서 .mkv 확장자를 가진 모든 파일의 전체 경로 배열을 가져온다.
                mkvFiles = Directory.GetFiles(directoryPath, "*.mkv");

                // 파일 목록을 TextBox에 출력
                textBox1.Clear();
                mkvFilesInDateTime = new DateTime[mkvFiles.Length];

                for (int i = 0; i < mkvFiles.Length; i++)
                {
                    // Path.GetFileName(file)를 사용하면 파일 이름(확장자 포함)을 가져올 수 있다.
                    mkvFiles[i] = Path.GetFileNameWithoutExtension(mkvFiles[i]);

                    // 파일 이름에서 날짜와 시간 부분을 추출하여 mkvFilesInDateTime 배열에 저장
                    mkvFilesInDateTime[i] = DateTime.ParseExact(mkvFiles[i], "yyyyMMdd-HHmmss", null);

                    textBox1.AppendText(mkvFiles[i] + Environment.NewLine);
                }
            }
            else
            {
                MessageBox.Show("지정된 폴더를 찾을 수 없습니다.");
            }
        }
    }
}

 

위 코드를 빌드하고 실행한다.

 

Video 폴더에는 위와 같은 파일이 있다.

 

적당한 날짜(26/03/27)와 시간(09:36)을 선택하고 버튼을 클릭한다.

 

26/03/27 09:36보다 작으면서 가장 큰 시간의 파일은 20260327-091500.mkv이다.

 

반응형
Posted by J-sean
:
반응형

여러 가지 비동기 스레딩을 구현해 보자.

 

우선 인수와 리턴값이 없는 스레드다.

아래 3가지 코드는 모두 같은 내용이지만 다른 방식으로 작성되었다.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Counter()
        {
            int subCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Sub Thread counting... {subCounter++}");
            }
        }

        static void Main(string[] args)
        {
            Task task = new Task(Counter);
            task.Start();
                        
            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");                
            }

            task.Wait();
        }
    }
}

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Action counter = () =>
            {
                int subCounter = 0;
                for (int i = 0; i < 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"Sub Thread counting... {subCounter++}");
                }
            };

            Task task = new Task(counter);
            task.Start();
                        
            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");                
            }

            task.Wait();
        }
    }
}

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task task = Task.Run(() =>
            {
                int subCounter = 0;
                for (int i = 0; i < 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"Task counting... {subCounter++}");
                }
            });

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }

            task.Wait();
        }
    }
}

 

순서는 약간 달라질 수 있다.

 

 

이번엔 인수를 하나 받는 스레드를 만들어 보자. 아래 2가지 코드는 모두 같은 내용이지만 다른 방식으로 작성되었다.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Counter(object to)
        {
            int subCounter = 0;
            for (int i = 0; i < (int)to; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Sub Thread counting... {subCounter++}");
            }
        }

        static void Main(string[] args)
        {
            Task task = new Task(Counter, 5);
            task.Start();

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }

            task.Wait();
        }
    }
}

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Action<object> Counter = (object to) =>
            {
                int subCounter = 0;
                for (int i = 0; i < (int)to; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"Sub Thread counting... {subCounter++}");
                }
            };

            Task task = new Task(Counter, 5);
            task.Start();

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }

            task.Wait();
        }
    }
}

 

인수가 있는 경우, Task.Run() 메서드에서 람다식으로 코드를 만드는 건 의미가 없는 것 같다.

결과는 위와 같다.

 

 

이번엔 리턴값이 있는 스레드를 만들어 보자. 아래 2가지 코드는 모두 같은 내용이지만 다른 방식으로 작성되었다.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static int Counter()
        {
            int subCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Sub Thread counting... {subCounter++}");
            }

            return subCounter;
        }

        static void Main(string[] args)
        {
            Task<int> task = new Task<int>(Counter);
            task.Start();

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }

            task.Wait();
            Console.WriteLine($"Sub Thread finished with count: {task.Result}");
        }
    }
}

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task<int> task = Task<int>.Run(() =>
            {
                int subCounter = 0;
                for (int i = 0; i < 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"Sub Thread counting... {subCounter++}");
                }

                return subCounter;
            });

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }

            task.Wait();
            Console.WriteLine($"Sub Thread finished with count: {task.Result}");
        }
    }
}

 

Action은 리턴값이 없는 메서드 전용이기 때문에 이 경우 Action을 사용할 수 없다.

 

순서는 약간 달라질 수 있다.

 

 

이번엔 인수도 있고 리턴값도 있는 스레드를 만들어 보자.

아래 2가지 코드는 모두 같은 내용이지만 다른 방식으로 작성되었다.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static int Counter(object to)
        {
            int subCounter = 0;
            for (int i = 0; i < (int)to; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Sub Thread counting... {subCounter++}");
            }

            return subCounter;
        }

        static void Main(string[] args)
        {
            Task<int> task = new Task<int>(Counter, 5);
            task.Start();

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }

            task.Wait();
            Console.WriteLine($"Sub Thread finished with count: {task.Result}");
        }
    }
}

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Func<object, int> Counter = (object to) =>
            {
                int subCounter = 0;
                for (int i = 0; i < (int)to; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"Sub Thread counting... {subCounter++}");
                }

                return subCounter;
            };

            Task<int> task = new Task<int>(Counter, 5);
            task.Start();

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }

            task.Wait();
            Console.WriteLine($"Sub Thread finished with count: {task.Result}");
        }
    }
}

 

 

 

async, await를 사용해 보자.

 

아래 코드는 컴파일 에러가 발생한다. Main 함수에는 async가 단순하게 그냥 붙을 수 없다.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
    	// 컴파일 에러 발생
        static async void Main(string[] args)
        {
            await Task.Run(() => Count());

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }
        }

        static void Count()
        {
            int subCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Sub Thread counting... {subCounter++}");
            }

        }
    }
}

 

Main 함수에 단순하게 async가 붙으면 다음과 같은 에러가 발생한다.

Program does not contain a static 'Main' method suitable for an entry point

 

또, 아래와 같이 바꾸면 단일 스레드처럼 동작한다.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            await Task.Run(() => Count());

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }
        }

        static void Count()
        {
            int subCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Sub Thread counting... {subCounter++}");
            }

        }
    }
}

 

 

await 키워드는 이 비동기 작업(서브 스레드)이 완전히 끝날 때까지 여기서 기다리겠다는 의미이기 때문이다.

 

아래와 같이 바꿔보자.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            // await 없이 Task를 실행(시작)만 하고 변수에 담아둔다.
            // 이 시점부터 서브 스레드가 백그라운드에서 동작한다.
            Task countTask = Task.Run(() => Count());

            // 메인 스레드는 멈추지 않고 자신의 루프 작업을 동시에 수행한다.
            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                // 참고: 비동기 메서드(async) 내에서는 Thread.Sleep 대신 await Task.Delay를 사용하는 것을 권장.
                await Task.Delay(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }

            // 메인 스레드의 작업이 다 끝나면, 메인 프로세스가 종료되기 전에
            // 서브 스레드의 작업이 모두 완료되었는지 마지막으로 확인(대기).
            await countTask;
        }

        static void Count()
        {
            int subCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Sub Thread counting... {subCounter++}");
            }
        }
    }
}

 

아니면 아래와 같이 다른 함수에서 async, await를 사용하도록 코드를 바꾼다.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Count();

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }
        }

        static async void Count()
        {
            await Task.Run(async () =>
            {
                int subCounter = 0;
                for (int i = 0; i < 5; i++)
                {
                    // Use Task.Delay instead of Thread.Sleep to avoid blocking the thread
                    await Task.Delay(1000);
                    Console.WriteLine($"Sub Thread counting... {subCounter++}");
                }
            });
        }
    }
}

 

 

 

이번엔 반환값이 있는 함수를 살펴보자.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task<int> taskResult = Count();

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }

            int result = taskResult.Result;
            Console.WriteLine($"Result from Sub Thread: {result}");
        }

        static async Task<int> Count()
        {
            return await Task.Run(() =>
            {
                int subCounter = 0;
                for (int i = 0; i < 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"Sub Thread counting... {subCounter++}");
                }

                return subCounter;
            });
        }
    }
}

 

 

위 코드를 아래와 같이 바꾸면 단일 스레드처럼 작동하게 된다. 조심하자.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            int taskResult = Count().Result;
            // Task가 시작되자마자 바로 Result를 요구하고 있다.
            // 메인 스레드가 진행하지 못하게 된다.
            Console.WriteLine($"Result from Sub Thread: {taskResult}");

            int mainCounter = 0;
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Console.WriteLine($"Main Thread counting... {mainCounter++}");
            }
        }

        static async Task<int> Count()
        {
            return await Task.Run(() =>
            {
                int subCounter = 0;
                for (int i = 0; i < 5; i++)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"Sub Thread counting... {subCounter++}");
                }

                return subCounter;
            });
        }
    }
}

 

 

 

Parallel 클래스를 사용해서 간편하게 스레드를 사용해 보자.

 

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Parallel.For(0, 10, DoSomething);

            // Alternatively, you can use a lambda expression:
            //Parallel.For(0, 10, i =>
            //{
            //    DoSomething(i);
            //});
        }

        static void DoSomething(int i)
        {
            Thread.Sleep(1000);
            Console.WriteLine($"Job {i} completed.");
        }
    }
}

 

 

하나씩 처리하면 10초가 걸리는 작업이 10개의 스레드가 생성되어 1초 만에 끝난다. (스레드는 CPU 코어의 갯수에 따라 다르게 생성된다)

 

반응형
Posted by J-sean
:
반응형

다른 콘솔 프로그램을 실행하고 결과를 확인해 보자.

 

using System;
using System.Diagnostics;
using System.IO;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // ProcessStartInfo 설정
            ProcessStartInfo startInfo = new ProcessStartInfo();
            //startInfo.FileName = "cmd.exe"; // 실행할 프로그램 (예: cmd)
            //startInfo.Arguments = "/c dir"; // 프로그램 인자 (예: 현재 디렉토리 파일 목록)
            startInfo.FileName = "cmd.exe";
            startInfo.Arguments = "/c dir";
            startInfo.UseShellExecute = false; // 쉘 사용 안 함 (리다이렉션 필수 설정)
            startInfo.RedirectStandardOutput = true; // 표준 출력 리다이렉션
            startInfo.CreateNoWindow = true; // 콘솔 창 띄우지 않음

            // 프로세스 실행
            using (Process process = Process.Start(startInfo))
            {
                // 3. 출력 내용 읽기
                using (StreamReader reader = process.StandardOutput)
                {
                    // 전체 결과 가져오기. 너무 길면 메모리 문제 발생 가능
                    //string result = reader.ReadToEnd();
                    //Console.WriteLine("--- 외부 프로그램 결과 ---");
                    //Console.WriteLine(result);
                    //Console.WriteLine("--------------------------");

                    // 한 줄씩 읽기
                    string line;
                    Console.WriteLine("--- 외부 프로그램 결과 ---");
                    while ((line = reader.ReadLine()) != null)
                    {
                        Console.WriteLine(line);
                        //Console.Write(line + Environment.NewLine);
                    }
                    Console.WriteLine("--------------------------");
                }
                process.WaitForExit(); // 프로그램 종료 대기
            }
        }
    }
}

 

 

2025.04.14 - [C, C++] - 명령창(cmd)을 열지 않고 명령 실행하고 결과 받아오기 popen(pipe open), pclose(pipe close)

 

반응형
Posted by J-sean
:
반응형

문화에 따라 다르게 표현되는 데이터를 일관되게 표현해 보자.

 

namespace ConsoleApp1
{
    using System.Globalization;

    internal class Program
    {
        static void Main(string[] args)
        {
            var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
            foreach (var ci in allCultures)
            {
                // Display the name of each culture.
                Console.Write($"{ci.EnglishName} ({ci.Name}): ");
                // Indicate the culture type.
                if (ci.CultureTypes.HasFlag(CultureTypes.NeutralCultures))
                    Console.Write(" NeutralCulture");
                if (ci.CultureTypes.HasFlag(CultureTypes.SpecificCultures))
                    Console.Write(" SpecificCulture");
                Console.WriteLine();
            }

            Console.WriteLine("Current culture is {0}", CultureInfo.CurrentCulture.Name);

            double value = 1234.56;
            
            Console.WriteLine(value);
            Console.WriteLine(value.ToString());
            // The current culture is a property of the executing thread.
            Console.WriteLine(value.ToString(CultureInfo.CurrentCulture));
            // The invariant culture is culture-insensitive; it's associated with the English
            // language but not with any country/region.
            Console.WriteLine(value.ToString(CultureInfo.InvariantCulture));

            CultureInfo.CurrentCulture = new CultureInfo("de-DE");

            Console.WriteLine("Current culture is {0}", CultureInfo.CurrentCulture.Name);
            Console.WriteLine(value);
            Console.WriteLine(value.ToString());
            Console.WriteLine(value.ToString(CultureInfo.CurrentCulture));
            Console.WriteLine(value.ToString(CultureInfo.InvariantCulture));

            CultureInfo.CurrentCulture = new CultureInfo("en-US");

            Console.WriteLine("Current culture is {0}", CultureInfo.CurrentCulture.Name);
            Console.WriteLine(value);
            Console.WriteLine(value.ToString());
            Console.WriteLine(value.ToString(CultureInfo.CurrentCulture));
            Console.WriteLine(value.ToString(CultureInfo.InvariantCulture));
        }
    }
}

 

 

반응형
Posted by J-sean
:
반응형

난수를 만들고 섞어보자.

 

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Random random = new Random(); // 난수 생성기

            // byte 배열 난수 생성
            byte[] bytes = new byte[10];            
            random.NextBytes(bytes);
            foreach (byte b in bytes)
            {
                Console.Write($"{b}, ");
            }
            Console.WriteLine();

            // 난수 섞기
            random.Shuffle<byte>(bytes);
            foreach (byte b in bytes)
            {
                Console.Write($"{b}, ");
            }
            Console.WriteLine();

            // 문자열 리스트 섞기
            List<string> fruits = new List<string> { "사과", "바나나", "딸기", "오렌지", "수박", "멜론", "키위" };
            string[] fruitsArray = fruits.ToArray();
            random.Shuffle<string>(fruitsArray);
            
            foreach (string fruit in fruitsArray)
            {
                Console.Write(fruit + ", ");
            }
            Console.WriteLine();

            // 정수 리스트 생성
            List<int> numbers = Enumerable.Range(1, 10).ToList();
            foreach (int i in numbers)
            {
                Console.Write(i + ", ");
            }
            Console.WriteLine();

            // 정수 배열 섞기
            int[] numbersArray = numbers.ToArray();
            random.Shuffle<int>(numbersArray);
            foreach (int i in numbersArray)
            {
                Console.Write(i + ", ");
            }
            Console.WriteLine();

            // 섞인 정수 배열 일부 선택
            foreach (int i in numbersArray[2..^5]) // '^'는 뒤에서 부터 인덱싱. ^5는 파이썬의 -5와 동일.
            {
                Console.Write(i + ", ");
            }
            Console.WriteLine();

            // 리스트 컴프리헨션 기능
            List<int> squared = (from x in numbers
                                 where x % 2 == 0 // 짝수라는 조건이 필요 없다면 삭제 가능.
                                 select x * x).ToList();
            foreach (int i in squared)
            {
                Console.Write(i + ", ");
            }
            Console.WriteLine();
        }
    }
}

 

 

정답(2)을 포함하는 선택지를 만들어 보자.

 

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Random random = new Random(); // 난수 생성기

            // 1~10 정수 리스트 생성
            List<int> numbers = Enumerable.Range(1, 10).ToList();

            // 특정 숫자(2)를 포함하는 선택지 4개 만들기
            List<int> choices = new List<int>();
            choices.Add(2); // 2는 무조건 포함
            while (choices.Count < 4)
            {
                int i = random.Next(0, numbers.Count);
                if (!choices.Contains(numbers[i]))
                    choices.Add(numbers[i]);
            }

            // 선택지 섞기
            choices = choices.Shuffle<int>().ToList();
            foreach (int i in choices)
            {
                Console.Write(i + ", ");
            }
            Console.WriteLine();
        }
    }
}

 

 

난수 생성기 없이 만들 수도 있다.

 

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<int> numbers = Enumerable.Range(1, 10);
            numbers = numbers.Shuffle();
            // 1~10 정수를 생성하고 섞기

            IEnumerable<int> choices = [2]; // 선택지에 2는 무조건 포함

            foreach (int n in numbers)
            {
                if (!choices.Contains(n))
                    choices = choices.Append(n);

                if (choices.Count() >= 4)
                    break;
            }

            choices = choices.Shuffle();

            foreach (int i in choices)
                Console.Write(i + ", ");
        }
    }
}

 

 

반응형
Posted by J-sean
: