C# deadlock prevent

2023. 6. 23. 21:24CSharp/Advance

반응형

Winform project 를 하나 생성 하고 다음과 같은 code 를 작성해 보자

textbox 하나 button 하나 만들고 테스트 해보자

public static async Task<string> GetJsonAsync(Uri uri)
{
    // (real-world code shouldn't use HttpClient in a using block; this is just example code)
    using (var client = new HttpClient())
    {
        var jsonString = await client.GetStringAsync(uri);
        return jsonString;
    }
}

private void button1_Click(object sender, EventArgs e)
{
    textBox1.Clear();
    var jsonTask = GetJsonAsync(new Uri($"https://www.naver.com/"));
    var text =jsonTask.Result;
    textBox1.Text = text;
}

위와 같이 code 를 작성하면 button1 을 click 한 순간 deadlock 이 걸린다. 

deadlock 이 걸리는 이유는 자세하게 여러곳에서 설명하고 있다.  (아래를 참고하자)

https://medium.com/rubrikkgroup/understanding-async-avoiding-deadlocks-e41f8f2c6f5d

 

Understanding Async, Avoiding Deadlocks in C#

You ran into some deadlocks, you are trying to write async code the proper way or maybe you’re just curious. Somehow you ended up here, and…

medium.com

해결하는 방법도 몇가지 있다

그중에서 가장 안전하고 완벽한 방법은 하나 뿐이다. 

async method 를 호출 하는 상위 method 를 async 하게 만들면 된다. 

일단 코드로 확인해 보자

private async void button1_Click(object sender, EventArgs e)
{
    textBox1.Clear();
    var jsonTask = await GetJsonAsync(new Uri($"https://www.naver.com/"));
    //var text =jsonTask.Result;
    textBox1.Text = jsonTask;
}

void 앞에 async 를 두고 await 을 이용해 비동기 코드를 호출 하였다. 

즉 이문제의 근본적인 원인은 비동기 코드를 동기 코드에서 호출했기 때문이다. 

비동기 코드는 동기 코드에서 호출 하면 안된다. 

이게 ms 의 대표적인 답변이다. 이 외에 모든 해결책에서는 deadlock 이 발생할 수 있다. 

하지만 위 코드를 보면 알겠지만 저런식으로 간단하게 해결되는 경우는 없다고 봐도 된다. 

애초에 호출하는 코드를 async 로 간단하게 수정할 수 있었다면 이러한 문제가 계속 발견되지 않았을 것이다. 

현장에 대한 개념이 없는 ms 에 해결책 이라고 할 수 있다. 

그래서 울며 겨자 먹기로 다른 해결책들을 찾아봐야 한다. 

(완벽한 방법은 위 방법 뿐이다. 다른 방법들은 다 그들만에 문제점을 내포하고 있으니

충분히 test 후 사용하기 바란다. 아래 해결책도 deadlock 상황을 완전히 벗어난 방법인지는 알수 없다.)

 

우리는 아래와 같은 helper 를 이용하여 처리 할 수 있다. 

AsyncHelper.cs

public static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None,
        TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        var cultureUi = CultureInfo.CurrentUICulture;
        var culture = CultureInfo.CurrentCulture;
        return _myTaskFactory.StartNew(() =>
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = cultureUi;
            return func();
        }).Unwrap().GetAwaiter().GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        var cultureUi = CultureInfo.CurrentUICulture;
        var culture = CultureInfo.CurrentCulture;
        _myTaskFactory.StartNew(() =>
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = cultureUi;
            return func();
        }).Unwrap().GetAwaiter().GetResult();
    }
}

 

이제 AsyncHelper 를 이용하여 해결해보자

private void button1_Click(object sender, EventArgs e)
{
    var text = AsyncHelper.RunSync(() => GetJsonAsync(new Uri($"https://www.naver.com/")));
    textBox1.Text = text;
}

이제 정상적으로 처리 되는 걸 확인 할 수 있다. 

 

 

 

관련영상

 

반응형

'CSharp > Advance' 카테고리의 다른 글

Refactroing - HttpClient  (1) 2023.10.23
Refactoring - Encryption Hash Helper  (0) 2023.10.16
Singleton  (0) 2022.02.25
Prototype  (0) 2022.02.24
Factory Method  (0) 2022.02.23