2022년 9월 7일 수요일

C# Async/Await Task 속도가 느릴 때 (TaskCompletionSource)

고전적인 개발 방법으로는 작업이 끝났는지 확인하기 위해서

작업 내용을 확인하는 루프를 만들어서 확인을 하는데


나는 이걸 mysql 풀링에 사용했었다.


그러니까 지금 사용 가능한 mysql 연결이 있는지를 task 에서

루프 돌면서 확인하는 방식으로 만들었던 것이다.


그런데 처음에 async/await task 방식이 문제라고 잘못 생각했던 것이

cpu 점유율을 너무 많이 먹었던 것이다.


ram이나 다른 리소스는 문제가 되지 않았다.

그저 cpu 점유율만 높았다. 그래서 이거 계속 써야 하나 싶었다.

그런데 써보니까 async 에서의 순차 실행이 참 편한 부분이 많았고

해결책을 찾아보니 내 코딩 스타일과 맞지 않았던 것이다.


루프 돌면서 쉬는 mysql 연결을 찾는 코드가 문제였는데

전체 확인하고나서 Task.Yield() 해서 한펌 쉬는 코드를 해놨는데

이게 유니티에서의 1프레임 단위가 아니었던 것이다.

결국 mysql 커넥션이 전부 사용중일때 많은 task 들이 yield를 하면서

루프를 도는데 이 텀이 너무 짧다보니 거의 무한루프(?) 돌듯이 계속 체크하면서

cpu를 다 먹었었다.


그래서 task 관련 공부를 더 했고 해결책을 찾았는데 그게

TaskCompletionSource였다.

값이 맞춰질때까지 대기하는 것이기 때문에 mysql을 다 쓴 후 반환하는 코드에서

mysql 연결 인덱스를 맞춰주니 yield를 사용할 필요가 없어서 cpu 사용율이

획기적으로 내려갔다.


사실 이건 내 코딩 스타일이 오래되서 그런거 같다. 하여간 문제는 해결이 되었고

이제 cpu 점유율 문제는 사라졌다.


2022년 4월 3일 일요일

C# CSV 파싱(여러줄 가능)

이전에는 데이터를 엑셀 데이터를 이용했는데 이건 좀 아닌거 같더라

그래서 CSV를 쓰려고 했는데 여러줄이 들어간 경우 때문에 안 쓰게 되었다.

이번에 현지화 관련 작업을 하면서 CSV를 쓰자고 생각이 들었고

CSV에서 어떤 방식으로 저장하는지를 연구해보았다. 일단 잘 동작한다. 

 
public static void ParsingCSV(string file_name,ref string[][] ret)
{
    ret = null;
    string all = ReadAllText(file_name);
    List<List<string>> lines = new List<List<string>>();
    List<string> ln = new List<string>();
    StringBuilder sb = new StringBuilder();
    bool is_string = false;
    int last = all.Length - 1;
    for(int i = 0; i < all.Length;++i)
    {
        Char c = all[i];
        if (is_string)
        {
            if (c == '"')
            {
                if (i < last && all[i + 1] == '"')
                { // " 2개가 연달아나오면 " 문자열을 써주면 된다.
                    sb.Append('"');
                    ++i;
                }
                else
                { // 문자열이 끝나는 상황
                    ln.Add(sb.ToString());
                    sb.Clear();
                    is_string = false;
                }
            }
            else sb.Append(c);
        }
        else
        {
            if (c == '"') is_string = true; // 문자열의 시작이다.
            else if (c == 0x0d)
            {
                ++i; // \r\n
                if (sb.Length > 0) ln.Add(sb.ToString());
                lines.Add(ln);
                sb.Clear();
                ln = new List<string>();
            }
            else if (c == ',')
            {
                if (sb.Length > 0)
                {
                    ln.Add(sb.ToString());
                    sb.Clear();
                }
                else ln.Add("");
            }
            else sb.Append(c);
        }
    }
    if (lines.Count <= 0) return;
    ret = new string[lines.Count][];
    for (int i = 0; i < ret.Length; ++i) ret[i] = lines[i].ToArray();
}
 "",",\r,\d, , 이 정도만 처리하면 되더라. 

 앞으로는 csv로 써보면서 문제 있으면 위 코드를 수정하게 될 것이다.

2022년 3월 12일 토요일

[윈도우 스토어] 워커홀릭

워커홀릭은 윈폼을 이용한 간단한 프로그램입니다.




구조

1. 상단 트리구조로 당장 업무에 필요한 내용을 정리합니다.

2. 하단 메모장에 필요한 내용을 정리합니다.


끝입니다.


시스템 트레이에 두고 관리할 수 있으며 언제든지 쉽고 빠르게 수정할 수 있습니다.

당장 가볍게 훅훅 들어오는 내용을 정리해 놓았다가 업무툴에 반영하기 좋습니다.

장기가 아니 당장 할 목록을 적었다가 지우면서 사용하면 좋습니다.


개인정보 취급 방침

이 앱은 설치형앱이고 아무런 인터넷 통신을 하지 않습니다.

어떠한 개인정보도 수집하지 않습니다.


2022년 3월 9일 수요일

IP로 국가 코드 얻기 KISA & DB BROWSER FOR SQLITE

 ip로 국가 확인 api


검색어로 구글 검색을 해보니 KISA에서 CSV를 배포하는 것을 알게 되었다.

일반적으로 GEOIP 검색해보면 유료 서비스가 있는데 돈 쓰는 것은 아깝고

PHP등에서는 예전 형식을 사용하는 것이 있는데 난 Dotnet을 사용하는데

데이터만 있으면 좋겠는데...  하고 아쉬워 했었다.


하여간 csv를 먼저 받도록 하자


kisa 국가별 ip


검색어로 검색을 하면

https://xn--3e0bx5euxnjje69i70af08bea817g.xn--3e0b707e/jsp/statboard/IPAS/ovrse/total/currentV4Addr.jsp

위 url로 들어가볼수 있다. (이상한 문자로 보이지만 주소창에 한글로 되어 있다.)


해당 사이트에서 csv를 받도록 한다.


내용은 아래와 같다




사실 여기서 필요한 정보는 국가코드,시작IP,끝IP 3가지다.

나머지열은 지워버린다. 그리고 1줄에 헤더 부분(한글) 도 지워버린다.

db browser for sqlite 를 켜고 데이터베이스를 만든다.

테이블은 안 만들어도 된다. (가져오기 하면 임시 테이블 만들어지니까 이거 수정하자)





파일을 지정하면 아래와 같이 나온다.


가져오기 하면 지 맘대로 테이블 만들어준다.


정보는 원하는대로 입력한다.


데이터가 제대로 입력된것을 볼 수 있다.


공격 들어오는 ip를 조회해보았다.



쿼리는 아래와 같다.

SELECT code from geoip where start <= '168.76.2.6' and end >= '168.76.2.6'

'168.76.2.6' <- 여기에 아이피 넣으면 되고 code 가 없을수도 있다.

이럴경우 특정 값으로 만들어 주고 나중에 다른 서비스를 이용해서 등록해주면

되지 않을까 싶다. 그리고 가능하면 주기적으로 확인해서 최신 데이터로 갈아주는것도

좋을 것이다.


%% 주의 %%

이거 문자열을 숫자형태로 변환해서 사용해야 합니다.

검색하면 나올건 sqlite에는 명령어가 없고 mysql 에서는 INET_ATON 명령어로

변환해서 사용해야 제대로 검색이 됩니다.

글을 수정하거나 새로 쓰기 귀찮아서 아래에 추가 합니다.

2022년 2월 27일 일요일

C# Litjson Extension

저는 C# 개발을 할때 json 라이브러리는 Litjson을 주로 사용합니다.


편하게 사용하기 위해서 정적 클래스 하나 만드러서 사용하다가 이번에는 익스텐션을 만들어 보았습니다.


확실히 편하게 사용할 수 있는 부분은 찾아보는 것이 좋은것 같습니다.


사용하는 litjson 라이브러는 0.17.0 버전입니다. 이전이나 이후도 별 상관은 없을것 같네요.

dotnet6로 만든 소스구요 null에 대한부분 ? 없애는 정도면 되지 않을까 싶네요.


사용법은

JsonData jd;

jd.GetInt();

jd.GetString();

아래 소스를 보시면 아시겠지만 어떻게든 값을 얻어옵니다.

타입 체크가 필요하면 하고 하면 될것이구요.

IsNumber의 경우 숫자 형태가 너무 많아서 추가했습니다.

필요한 것이 있으면 더 추가하면 될것이구요.

하여간 도움이 되면 좋겠습니다~


파일을 올릴수가 없네요.

소스입니다.

namespace LitJson
{
    internal static class LitjsonExtension
    {
        public static bool IsNumber(this JsonData jd)
        {
            switch (jd.GetJsonType())
            {
                case JsonType.Int:
                case JsonType.Long:
                case JsonType.Double: return true;
                default: return false;
            }
        }
        public static bool GetBool(this JsonData jd)
        {
            if (jd == null) return false;
            switch (jd.GetJsonType())
            {
                case JsonType.String: return Convert.ToBoolean((string)jd);
                case JsonType.Int: return Convert.ToBoolean((int)jd);
                case JsonType.Long: return Convert.ToBoolean((long)jd);
                case JsonType.Double: return Convert.ToBoolean((double)jd);
                case JsonType.Boolean: return (bool)jd;
                case JsonType.None:
                case JsonType.Object:
                case JsonType.Array:
                default: return false;
            }
        }
        public static int GetInt(this JsonData jd)
        {
            if (jd == null) return 0;
            switch (jd.GetJsonType())
            {
                case JsonType.String: return Convert.ToInt32((string)jd);
                case JsonType.Int: return (int)jd;
                case JsonType.Long: return Convert.ToInt32((long)jd);
                case JsonType.Double: return Convert.ToInt32((double)jd);
                case JsonType.Boolean: return Convert.ToInt32((bool)jd);
                case JsonType.None:
                case JsonType.Object:
                case JsonType.Array:
                default: return 0;
            }
        }
        public static long GetLong(this JsonData jd)
        {
            if (jd == null) return 0;
            switch (jd.GetJsonType())
            {
                case JsonType.String: return Convert.ToInt64((string)jd);
                case JsonType.Int: return Convert.ToInt64((int)jd);
                case JsonType.Long: return (long)jd;
                case JsonType.Double: return Convert.ToInt64((double)jd);
                case JsonType.Boolean: return Convert.ToInt64((bool)jd);
                case JsonType.None:
                case JsonType.Object:
                case JsonType.Array:
                default: return 0;
            }
        }
        public static double GetDouble(this JsonData jd)
        {
            if (jd == null) return 0;
            switch (jd.GetJsonType())
            {
                case JsonType.String: return Convert.ToDouble((string)jd);
                case JsonType.Int: return Convert.ToDouble((int)jd);
                case JsonType.Long: return Convert.ToDouble((long)jd);
                case JsonType.Double: return (double)jd;
                case JsonType.Boolean: return Convert.ToDouble((bool)jd);
                case JsonType.None:
                case JsonType.Object:
                case JsonType.Array:
                default: return 0;
            }
        }
        public static string? GetString(this JsonData jd)
        {
            if (jd == null) return null;
            switch (jd.GetJsonType())
            {
                case JsonType.Object:
                case JsonType.Array: return jd.ToJson();
                case JsonType.String: return (string)jd;
                case JsonType.Int: return Convert.ToString((int)jd);
                case JsonType.Long: return Convert.ToString((long)jd);
                case JsonType.Double: return Convert.ToString((double)jd);
                case JsonType.Boolean: return Convert.ToString((bool)jd);
                case JsonType.None:
                default: return null;
            }
        }
    }
}

2022년 1월 26일 수요일

[Cocos Creator] Scene, Prefab 머지 문제 방지하기

기존에 남긴 글을 보면

https://gogogodwish.blogspot.com/2022/01/cocos-creator-scene-prefab.html


svn이 바이너리는 머지를 하지 않는데 txt 형식의 파일은 머지를 하려고 한다. 

유니티에서는 문제가 없던 것이 코코스에서는 문제가 될 때가 있는데 프리팹, 씬 등

머지 하면 안 되는 파일들이 머지가 되면서 생기는 문제들이다.

문제 생기면 지우고 받고 또 문제 생기고를 반복하다가 혹시나 하는 생각이 들었다.

역시나 방법은 있었다. 머지를 하지 못하게 하면 되는 것이다.


아래는 TortoiseSVN을 이용해서 코코스에서 문제가 생기지 않게 설정하는 부분이다.




svn 설정을 바꿔주는데 기본값이

*.o *.lo *.la *.al .libs *.so *.so.[0-9]* *.a *.pyc *.pyo __pycache__ *.rej *~ #*# .#* .*.swp .DS_Store [Tt]humbs.db

이었는데 난 아래와 같이 수정했다.

*.o *.lo *.la *.al .libs *.so *.so.[0-9]* *.a *.pyc *.pyo __pycache__ *.rej *~ #*# .#* .*.swp .DS_Store [Tt]humbs.db *.fire *.prefab *.meta *.anim *.mtl *.plist *.tps *.txt *.json

뒤에 txt,json은 스파인에서 나오는 형식이 저렇게 나온다고 한다.


실제로 추가되는 내용은

*.fire *.prefab *.meta *.anim *.mtl *.plist *.tps *.txt *.json


svn 설정은 전역이기 때문에 본인에게 맞는 설정을 적용하면 될 것 같다.

C# Async/Await Task 속도가 느릴 때 (TaskCompletionSource)

고전적인 개발 방법으로는 작업이 끝났는지 확인하기 위해서 작업 내용을 확인하는 루프를 만들어서 확인을 하는데 나는 이걸 mysql 풀링에 사용했었다. 그러니까 지금 사용 가능한 mysql 연결이 있는지를 task 에서 루프 돌면서 확인하는 방식으로 만...