모니터링 자동화 - 비동기 변경

2023. 12. 24. 20:45개발/C#

2023.12.21 - [개발 기록/C#] - 모니터링 자동화 - Excel Write

 

모니터링 자동화 - Excel Write

2023.12.18 - [개발 기록/C#] - 모니터링 자동화 - Teamviewer 상태 관리 모니터링 자동화 - Teamviewer 상태 관리 2023.12.17 - [개발 기록/C#] - 모니터링 자동화 - 웹 크롤링 모니터링 자동화 - 웹 크롤링 C# Seleniu

iruk.tistory.com

 

자동화에 필요한 메인 프로세스들은 구현이 완료됐고

각 프로세스를 어떻게 관리할지

고민하다가

 

비동기 & 프로그레스바 로 결정

 

이유 

1. 사용자에게 진행상황을 표현

2. 동기식 사용 시, 프로세스가 끝날때 까지 다른 동작 불가능 ( ex) 프로그램 종료 )

3. 클린코드

 

비동기식 로직이 현재 내 상황에

적합해서, 전체 로직을 수정했다


전체 로직 수정 - 비동기식

static BackgroundWorker backgroundWorker = new BackgroundWorker();

public MainScreen()
{
    InitializeComponent();

    CloseChrome();

    backgroundWorker.WorkerReportsProgress = true;
    backgroundWorker.DoWork                += BackgroundWorker_DoWork;
    backgroundWorker.ProgressChanged       += BackgroundWorker_ProgressChanged;
    backgroundWorker.RunWorkerCompleted    += BackgroundWorker_RunWorkerCompleted;
}

 

BackgroundWorker를 전역값으로 선언 후

메인 화면 초기화 시, 관련 프로세스들을 이벤트 처리한다

private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    List<Func<bool>> processList = new List<Func<bool>>
    {
        () => RetryFailedProcess(() => webCrawlerMain.GetMonitoringSiteData()   , "웹 크롤링 오류"     , 3),
        () => RetryFailedProcess(() => teamviewerMgr.GetTeamviewerOffline()     , "팀뷰어 API호출 오류", 3),
        () => RetryFailedProcess(() => excelMgr.WriteExcel(filePathTextbox.Text), "Excel파일 오류"     , 3)
    };

    int totalProcesses = processList.Count;
    int currentProcess = 0;

    foreach (var process in processList)
    {
        if (backgroundWorker.CancellationPending)
        {
            e.Cancel = true;
            return;
        }

        if (process()) // 단계별 작업 성공 시, ( 웹 크롤링, 팀뷰어, 엑셀 )
        {
            currentProcess++;
            int progressPercentage = (int)((double)currentProcess / totalProcesses * 100);
            backgroundWorker.ReportProgress(progressPercentage);
        }
        else
        {
            e.Cancel = true;
            loadingScreen.progressStop = true;
            return;
        }
    }
}

 

비동기로 실행할 메인 프로세스다.

3가지의 프로세스를 비동기로 실행하며,

각 프로세스가 완료될때마다 프로그레스바 이벤트 및

실패 시 3회 재시도한다.

 

재시도 3회 초과 시 프로그램 종료 ( 에러판단 )

한줄씩 살펴보면

List<Func<bool>> processList = new List<Func<bool>>
{
    () => RetryFailedProcess(() => webCrawlerMain.GetMonitoringSiteData()   , "웹 크롤링 오류"     , 3),
    () => RetryFailedProcess(() => teamviewerMgr.GetTeamviewerOffline()     , "팀뷰어 API호출 오류", 3),
    () => RetryFailedProcess(() => excelMgr.WriteExcel(filePathTextbox.Text), "Excel파일 오류"     , 3)
};

 

먼저 bool 형 함수 List를 선언하고

List안에 각 프로세스를 담는다 ( 웹크롤링, 팀뷰어, 엑셀 작성 )

int totalProcesses = processList.Count;
int currentProcess = 0;

foreach (var process in processList)
{
    if (backgroundWorker.CancellationPending)
    {
        e.Cancel = true;
        return;
    }

    if (process()) // 단계별 작업 성공 시, ( 웹 크롤링, 팀뷰어, 엑셀 )
    {
        currentProcess++;
        int progressPercentage = (int)((double)currentProcess / totalProcesses * 100);
        backgroundWorker.ReportProgress(progressPercentage);
    }
    else
    {
        e.Cancel = true;
        loadingScreen.progressStop = true;
        return;
    }
}

 

procesList의 함수들을 실행하면서

process() 를 통해, 각 프로세스 함수의 성공여부를 확인한다 ( bool형 )

 

프로세스 성공 시, currentProcess 카운터를 증가

해당 값을 참조해 프로그레스바 게이지값 저장 ( 0~100% )

현재는 3개의 프로세스라 각각 100/3 으로 표현했다.

 

저장한 게이지값을 backgroundWorker의

ReportPrgress() 파라미터로 전달, 화면에 게이지를 표현한다.

 

프로세스 실패 시, RetryFailedProcess( 프로세스 함수, 3 ) 를 통해, 3회 재시도

private bool RetryFailedProcess(Func<bool> function, string errorMessage, int retryMax)
{
    int counter = 0;

    for (int i = 0; i < retryMax; i++)
    {
        if (function())
        {
            return true;
        }

        counter++;
        MessageBox.Show($"{errorMessage}, 재시도 합니다.\n재시도 횟수 : {counter}/{retryMax}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);

        if (counter == retryMax)
        {
            return false;
        }
    }

    return false;
}

 

이는 재시도 함수다.

파라미터로 전달받은 retryMax ( 재시도 횟수 최댓값 ) 로

function 을 재실행한다.

 

최종 실패 시 false 리턴.


프로그레스바 표현

 

일단 프로그레스바 화면을 별도로 구분했다.

처음에는 메인 화면에 표현했는데,

 

프로그레스바가 진행중인 상태에서,

사용자가  메인 화면의 버튼 요소들을

작동할 여지를 없애기 위함이다

 

비활성화로 표현할 수 있긴 하지만,

최대한 첫 화면을 간단하게 표현하고싶었다.

사용자가 헷갈리지 않게 !

public partial class LoadingScreen : Form
{
    private Thread progresBarCheckerThread = null;
    public bool progressStop = false;

    public LoadingScreen()
    {
        InitializeComponent();
        this.FormClosed += WhenFormClosed;
    }

    public string StatusText
    {
        get { return status.Text; }
        set { status.Text = value; }
    }

    public int ProgressBarValue
    {
        get { return progressBar.Value; }
        set { progressBar.Value = value; }
    }

    public string ProgressBarText
    {
        get { return progressBarText.Text; }
        set
        {
            if (progressStop)
            {
                return;
            }
            progressBarText.Invoke((MethodInvoker)delegate
            {
                progressBarText.Text = value;
            });
        }
    }

    public void SetLabelLocation(Point location)
    {
        status.Location = location;
    }

    public void Start()
    {
        progresBarCheckerThread = new Thread(progressBarChecker);
        progresBarCheckerThread.Start();
    }

    private void progressBarChecker()
    {
        while (ProgressBarValue <= 100)
        {
            if (progressStop) break;
            ProgressBarText = ProgressBarValue.ToString() + " %";
        }
    }

    private void WhenFormClosed(object sender, FormClosedEventArgs e)
    {
        progressStop = true;
        progresBarCheckerThread.Abort();

        Environment.Exit(0);
    }
}

 

프로그레스바 화면의 전체 소스다.

현재 게이지 값, 게이지 값 텍스트, 진행상황 안내 텍스트 등

 

필요한 값들을 get set으로 선언 후

외부에서 handle할 수 있도록 했다

폼 생성

public LoadingScreen()
{
    InitializeComponent();
    this.FormClosed += WhenFormClosed;
}

 

처음 폼이 생성되면,

WhenFormClosed 이벤트를 추가한다.

private void WhenFormClosed(object sender, FormClosedEventArgs e)
{
    progressStop = true;
    progresBarCheckerThread.Abort();

    Environment.Exit(0);
}

 

사용자가 프로그레스바 진행중인 상태에서

창을 닫았을 때, 방어코드가 없으니

프로그램 오류가 발생해서

 

사실 제일 마지막에 추가한 코드다

역시 예외처리를 미리 구상하는게 제일 어렵다..

당해봐야 그제서야 떠오른다

get set 처리

public string StatusText
{
    get { return status.Text; }
    set { status.Text = value; }
}

public int ProgressBarValue
{
    get { return progressBar.Value; }
    set { progressBar.Value = value; }
}

public string ProgressBarText
{
    get { return progressBarText.Text; }
    set
    {
        if (progressStop)
        {
            return;
        }
        progressBarText.Invoke((MethodInvoker)delegate
        {
            progressBarText.Text = value;
        });
    }
}

public void SetLabelLocation(Point location)
{
    status.Location = location;
}

 

위 3개 값들은, 화면상의 표현을 하기 위함이다.

 

웹 크롤링 / 팀뷰어 API / 엑셀 작성

1) 3가지 프로세스 별 진행상태,

2) 프로그레스 바 게이지,

3) 현재 게이지 값 텍스트

 

위 3가지 값을 다룬다.

 

마지막 SetLabelLocation() 은,

1) 에서 표현한 텍스트마다 길이가 달라서,

텍스트를 중앙에 맞추기 위한 함수다.

 

로그인 대기중 / 팀뷰어API 호출중

두가지를 예로들면

텍스트 길이가 달라서, 

텍스트가 조금 밀린다.

암튼 보기 불편해서 추가한 함수

 

로딩창 시작 & 프로그레스바 게이지 관리

public void Start()
{
    progresBarCheckerThread = new Thread(progressBarChecker);
    progresBarCheckerThread.Start();
}

private void progressBarChecker()
{
    while (ProgressBarValue <= 100)
    {
        if (progressStop) break;
        ProgressBarText = ProgressBarValue.ToString() + " %";
    }
}

메인 창의, 시작 버튼 클릭 시

위 로딩창의 Start() 함수가 호출된다.

 

Start() 함수에서, Thread로 프로그레스바를 관리한다.

현재 프로그레스바의 Value 를

로딩창 화면에 Text로 표현한다.

이렇게 실시간 값을

눈으로 확인할 수 있도록 처리했다


확실히, 코드를 짜기 전

구상을 많이 해봐야

 

예외상황을 많이 예방할 수 있는데

난 좀 더 경험을 쌓아야

익숙해질 것 같다

 

아직은 100% 예방이 되지 않네

 

그때그때 수정하는게 바람직하진 않다

프로그램 시나리오를 문서로 작성하고

그에 맞는 함수단위 코딩을 시작해야

좀 더 생산성있고 효율적인 개발이 될 것 같다