NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# C# .NET 매크로 프로그램 만들기. (패러럴 처리가 가능한 비동기 그룹 액션 만들기)

    페이지 정보

    본문

    안녕하세요. 엔지엠소프트웨어입니다. 그동안 엔지엠 매크로 6에서 제공하지 않았던 새로운 기능을 소개하게 되었습니다. 비동기 그룹은 하위로 추가한 액션들을 매인 스크립트와 별개로 동작시킬 수 있는 막강한 액션입니다. 일반적으로 병렬 함수(패러럴 펑션: Parallel Function)라고 부릅니다. 병렬 함수는 여러가지 일을 동시에 할 수 있도록 해줍니다. 참고로, 병렬 함수를 여러개 사용하는 경우 몇가지 제약이 따르는데요. 동시성에 위배되는 행위를 처리할 수 없다는 것입니다.

     

    동시성 위배는 단순히 말해서 동시에 하나의 상태를 변경할 수 없다는 뜻입니다. 우리가 컴퓨터를 사용할 때 뭔가 동시에 처리되는것처럼 보이지만, 사실은 내부에서 매우 빠르게 동작하기 때문에 동시에 일을 하는것처럼 느낄뿐입니다. 이는 하나의 코어를 대상으로 설명하는 내용입니다. 만약, 코어가 여러개라면 각각의 코어가 일을 하기 때문에 문제가 되지는 않습니다. 하지만, 이들 코어가 하나의 자료 공간을 공유하면 문제가 됩니다. 각각의 자료 공간을 사용해야합니다. 엔지엠 매크로도 이런 제약은 동일하게 적용됩니다. 컴퓨터에서 일을하기 때문입니다.

     

    A스크립트에 비동기 그룹이 2개 있다고 생각 해보세요. A스크립트의 변수 V가 있다고 생각해봅시다. 이 때 비동기 그룹 2개가 동시에 V 변수에 접근해서 값을 업데이트한다면 어떤일이 벌어질까요? 프로그램의 행이 발생합니다. 일반적으로 병목현상은 시간이 지나면 문제가 해결되지만, 행은 스스로 해결될 수 없습니다. 스크립트를 멈추거나 프로그램을 종료해야 합니다. 보통 프로그램이 프리징 걸렸다라고 표현하는 것들이 전부 여기에 속합니다.

    eSSyURc.png

     

     

    Hang (놀다)

    위에서 잠시 언급한바와 같이 A라는 변수에 B와 C 함수가 업데이트를 한다고 생각 해보세요. 이 때 컴퓨터는 A 변수를 업데이트할 수 있는 권리를 누구에게 줄것인지 판단해야 합니다. 물론, 순서대로 접근하면 아무런 문제가 발생하지 않습니다. 그리고, 사람도 문제가 안됩니다. 철수와 영희가 서로 발표하겠다고 동시에 손을 들었을 때 선생님은 잠시 망설이긴 하겠지만 누구 하나를 선택할겁니다. 하지만, 컴퓨터는 선택할 수 없습니다. 그래서, 누구를 시킬지 무한정 대기하게 되는 문제가 발생합니다.

     

     

    Freezing (얼다)

    프리징은 행과 같은 의미로 많이 사용되지만, 근본적인 원인은 다르게 발생합니다. 일반적으로 프리징은 하드웨어 문제와 소프트웨어 문제로 나눌 수 있습니다. 대부분 제대로 만든 멋진 프로그램들은 문제가 발생할것으로 예상되는 부분에 타임아웃을 걸어서 이 문제가 발생하지 않도록 방지합니다. 홈페이지에 접속하거나 카카오톡과 같은 메신저에 메세지를 보낼 때 서버가 응답하지 않거나 네트워크가 느린 경우 프리징이 걸립니다. 하지만, 대부분의 프로그램들은 어느정도 응답이 없으면 연결을 종료시키고 다음 행동을 수행합니다. 그래서, 행과는 다르게 프리징은 스스로 풀리는 경우가 많습니다. 물론, 이런 문제를 예상하지 못했다면 행과 같이 무한 대기하게 됩니다.

     

    Deadlock (교착상태)

    교착 상태는 읽기 전용인 경우 문제가 되지 않을겁니다. 하지만, 어떤 리소스가 상태를 변경하기 위해서 특정 프로세스나 함수에의해 묶인 경우 문제가 됩니다. 예를 들어서 A 변수에 Thread safe를 보장하기 위해 Lock을 걸었다고 생각 해보세요. 이런 경우 다른 프로세스는 더이상 A 변수에 접근할 수 없게됩니다. A 변수에 락을 건 프로세스가 작업을 마칠 때까지요. 그런데, 락을 건 프로세스가 락을 풀기 위한 조건을 만족하지 못하면 무한정 락을 걸고 있을겁니다. 이 때 다른 프로세스가 락이 걸린 A 변수에 접근하려고 할 때 데드락이 발생하게 됩니다.

     

    위에서 행, 프리징, 데드락과 같은 OS에서 리소스를 처리하는 여러가지 방법들을 알아봤습니다. 이러한 문제들을 회피 또는 우회하기 위한 몇가지 방법들이 존재합니다. 가장 심플한 방법은 순서에 관계 없이 동시성을 보장하지 않는겁니다. 어떤 프로세스 또는 스레드나 타스크가 업데이트할지 알 수 없기 때문에 하나의 코어만 사용하면서 동시에 접근할 수 없도록 막는겁니다. 비동기 그룹도 이와 같은 방식을 사용합니다. 그렇기 때문에 접근할 수 있는 타이밍과 흐름도를 사용자가 잘 구성해야 합니다.

     

    기존 엔지엠 매크로 6의 그룹 액션 확장 버전이기 때문에 대부분의 내용은 동일합니다. 다만, 비동기로 처리할 수 있도록 속성이 하나 추가되었습니다.

    using Ai.Interface;
    using System.ComponentModel;
    
    namespace Ai.Model.Action.Function
    {
        [Serializable]
        public class GroupModel : ActionModel, Ai.Interface.IIndependentModel
        {
            [LocalizedCategory("Action")]
            [LocalizedDisplayName("UseAsynchronous")]
            [LocalizedDescription("UseAsynchronous")]
            [Browsable(true)]
            [DefaultValue(false)]
            public bool UseAsynchronous { get; set; }
    
            public override string? Execute(IPlayer player)
            {
                if (UseAsynchronous &&!Ai.Common.Helper.NullCheckAndWriteLine(player, nameof(ID), ID))
                    return null;
    
                if (UseAsynchronous && Actions?.Count > 0)
                    player.Function(ID, Actions.Cast<IModel>().ToList());
    
                return null;
            }
        }
    }

     

    코드 내용을 보면 알겠지만, 플레이어의 펑션 메소드를 사용해서 별도의 공간을 할당받고, 거기서 매인 스크립트와 분리된 상태로 타스크를 생성합니다.

    public void Function(string funcId, List<IModel> actions)
    {
        Run(funcId, actions.Cast<ActionModel>().ToList(), true);
    }

     

    모델과 엔진은 참조 레벨이 다릅니다. 모델이 더 낮은 등급이기 때문에 어쩔 수 없이 인터페이스를 사용해서 데이타를 전달하고, 모델의 액션으로 캐스팅해줘야 합니다. 플레이어의 타스크 관리용 목록에서 함수를 추가합니다.

    private Dictionary<string, Task> _taskList = new Dictionary<string, Task>();

     

    스크립트 아이디에 함수명이 들어갑니다. 비동기 그룹을 사용하려면 무조건, 아이디를 설정해야 합니다.

    _taskList.Add(scriptId, Task.Run(() =>
    {

     

    그룹 액션이 비동기라면 아이디를 체크한 후 사용자에게 아이디는 필수 입력값이라는 메세지를 표시해야 합니다.

    if (UseAsynchronous &&!Ai.Common.Helper.NullCheckAndWriteLine(player, nameof(ID), ID))
        return null;

     

    특정 속성들은 빈 값을 허용하지 않아야 하므로, 아래와 같이 자주 사용하는 메소드를 하나 추가했습니다. 빈 값을 체크한 후 자동으로 메세지까지 만들어주는 메소드입니다.

    public static bool NullCheckAndWriteLine(Ai.Interface.IPlayer player, string propertyName, string? value)
    {
        if (string.IsNullOrEmpty(value))
        {
            player.Manager.Output.WarningWriteLine($"[{propertyName}] {player.Manager.Client.ResxMessage.GetString("RequiredPropertyValue")}");
            return false;
        }
    
        return true;
    }

     

    비동기 모드를 원할하게 테스트하려면 프로세스를 건너뛸 수 있게 액션 이동이 필요합니다. 위에서 아래로 순차적으로 실행된다면 비동기 처리인지 확인할 수 없기 때문입니다. 따라서, 액션 이동 모델도 하나 추가해야 합니다. 코드는 아래와 같이 단순합니다.

    using Ai.Interface;
    using System.ComponentModel;
    
    namespace Ai.Model.Action.Function
    {
        [Serializable]
        public class GotoModel : ActionModel, Ai.Interface.IActionIdConverter
        {
            [LocalizedCategory("Action")]
            [LocalizedDisplayName("GotoID")]
            [LocalizedDescription("GotoID")]
            [Browsable(true)]
            [DefaultValue(null)]
            [TypeConverter(typeof(TypeConverter.ActionIdConverter))]
            public string? GotoID { get; set; }
    
            public override string? Execute(IPlayer player)
            {
                if (!Ai.Common.Helper.NullCheckAndWriteLine(player, nameof(GotoID), GotoID))
                    return null;
    
                return GotoID;
            }
        }
    }

     

    우리는 이미 플레이어서 조건을 처리하는 방법을 구현 해두었습니다. 조건을 처리할 때 참과 거짓에 따라 액션 이동이 발생합니다. 이 로직을 액션 이동에서도 동일하게 사용합니다. GoToID는 조건의 참과 거짓뿐만 아니라 액션 루틴을 이동시킬 때 사용하는 범용적인 메소드입니다.

    DztTzs3.png

     

    GoToID 메소드는 전체 Actions 목록에서 인자로 넘겨 받은 아이디가 있으면 해당 아이디의 위치를 찾아서 인덱스를 변경합니다.

    Zyi5Qgp.png

     

    여기까지 비동기 그룹 액션에 대해서 코드를 작성 해봤는데요. 이제 실제로 그렇게 동작하는지 테스트를 해볼께요. 아래와 같이 작성하고, 실행 해보세요. 지금은 0.5초마다 윈도우 바탕화면의 아이콘을 순차적으로 클릭합니다.

     

     

    이번에는 비동기 모드로 변경하고, 실행 해보세요. 아이디 설정을 안해서, 하단 출력창에 경고 메세지가 표시되었네요.

    ZhReF6A.png

     

     

    아이디를 설정하고, 다시 실행해보도록 하겠습니다. 

     

     

    비동기 설정 여부에 따라서 동작이 다른것을 확인하셨나요? 우선, 비동기가 아닌 경우에는 마우스 클릭이 0.5초 간격으로 실행됩니다. 당연한 이야기지만, 위에서 아래로 순차적으로 실행되기 때문입니다. 그런데, 비동기를 사용함으로 변경하고 실행하면 처음 0.5초 클릭과 두번째 0.5초 클릭이 동시에 따닥 발생하는걸 확인할 수 있습니다. 비동기 그룹이 병렬로 실행되기 때문에 자신의 동작들을 모두 설정해서 개별 실행되고, 매인 스크립트도 개별 실행되기 때문에 지연값을 준 0.5초가 병렬로 처리됩니다.

     

    말이 좀 어렵고 복잡한데요. 비동기 그룹 0.5초 후 클릭이 동작하면서 그룹 밖에 있는 0.5초 클릭도 같이 실행되었기 때문에 시간차가 없게 느껴진겁니다. 실제로 그렇게 동작했고요. 완전 동시는 아닙니다. 비동기 처리를 위해 윈도우로부터 가용할 수 있는 리소스를 요청하고, 윈도우가 승인 후 리소스를 내려줘야 그룹이 실행될 수 있습니다. 이런 전처리 과정들로 인해 완전 동일하게 실행되지는 않습니다.

     

    이번엔 좀 더 복잡하게 구성해볼까요?

    rLHOk75.png

     

     

    이 시나리오는 이전 버전의 엔지엠 매크로 문제를 극복하기 위해 개선된 내용을 테스트하는 스크립트입니다. 하나의 스크립트에서 동시에 액션들이 실행될 수 없었는데요. 새로운 버전에서는 비동기 함수를 사용할 수 있습니다. 동시에 실행되려면 무조건 스크립트가 분리되어서 서브 스크립트나 예약 작업등과 같은 액션으로 처리해야 했습니다. 이들은 하나의 스크립트가 아니기 때문에 데이터를 전달하기 위해서 글로벌 변수나 패어런트 변수를 사용해야 합니다. 여전히 이 방식을 사용할 수 있지만, 이런 번거로움을 해결하려면 비동기 그룹을 사용하는게 좋습니다.

     

    이 스크립트에는 마우스 클릭이 3개 있습니다. 이들은 각각 윈도우 바탕화면의 아이콘을 순서대로 클릭합니다. 그런데 패러럴하게 동작하는 비동기 그룹안에 액션 이동이 있습니다. 액션 이동에서 마우스 클릭 C로 보내도록 해두었기 때문에 그 아래에 있는 액션 이동은 만날 수 없게 됩니다.

    iueBjeJ.png

     

     

    만약, 이 스크립트가 비동기로 동작하지 않는다면 아래 액션 이동에서 d로 보내기 때문에 마지막 클릭은 작동하지 않을겁니다.

    xMqOeVA.png

     

     

    매크로를 실행하면 첫번째, 두번째 세번째 클릭 모두 동작하는걸 확인할 수 있습니다.

     

     

    하단 아웃풋에서도 확인이 가능합니다.

    XaRGjbU.png

     

     

    비동기로 처리했어도 루트에 있는 액션으로 잘 이동합니다. 루트에 액션 이동은 건너뛰고 비동기 그룹의 액션 이동이 작동했습니다. 비동기 그룹을 잘 활용하면 많은 부분에서 매크로 로직이 개선될 것으로 기대하고 있습니다. 예를 들어서 특정 창에서 이미지를 서치하면서 계속 업무를 처리할 수 있습니다. 특정 창에서 이미지가 발견되면 액션 이동으로 추가적인 처리를 하고, 업무를 게속 이어나갈 수 있을겁니다. 이런 복합적이고 다양한 시나리오에서 스크립트를 좀 더 쉽게 구성할 수 있을듯 합니다.

     

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.