잘못된 SqlConnection

// 코드 1

using (var connection = new SqlConnection("접속문자열"))

{

    connection.Open();

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

}


<코드 1>을 실행하면 크래쉬가 발생

이유는 간단 SqlConnection가 스레드 세이프 하지 않기 때문.


위의 코드를 올바르게 수정하려면 아래와 같이 변경.

// 코드 2

Parallel.For(1, 1000, x =>

{

    using (var connection = new SqlConnection("접속문자열"))

    {

        connection.Open();

        var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper

    }

});



<코드 2>는 안전하지만 아마 위 방식은 원하는 방식이 아닐 것이다.



ThreadLocal

위의 문제를 ThreadLocal을 사용하여 해결해 보자


// 코드 3

using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }))

{

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

}



성능 테스트를 해보면 싱글 스레드를 사용하면 16초, <코드 2>는 5초, <코드 3>은 2초



<코드 3>은 안전한가?

<코드 3>은 위험한 코드.

이유는 연결한 후 Dispose 하지 않기 때문.

ThreadLocal의 Dispose는 어디까지는 ThreadLocal의 Dispose이기 때문에 그 안의 객체를 Dispose 해주지 않는다.


이 문제는 trackAllValues 라는 옵션을 사용하여 간단하게 해결 가능!



// 코드 4

using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }

    , trackAllValues: true)) // ThreadLocalの.Values 프로퍼티의 참조를 유효화 한다

{

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

    });


    // 생성된 모든 Connection을 일괄적으로 Dispose

    foreach (var item in connection.Values.OfType<IDisposable>()) item.Dispose();

}



단 trackAllValues는 닷넷프레임워크 4.5 부터 사용 가능




trackAllValues 랩퍼 클래스 사용


public static class DisposableThreadLocal

{

    public static DisposableThreadLocal<T> Create<T>(Func<T> valueFactory)

        where T : IDisposable

    {

        return new DisposableThreadLocal<T>(valueFactory);

    }

}


public class DisposableThreadLocal<T> : ThreadLocal<T>

    where T : IDisposable

{

    public DisposableThreadLocal(Func<T> valueFactory)

        : base(valueFactory, trackAllValues: true)

    {

    }


    protected override void Dispose(bool disposing)

    {

        var exceptions = new List<Exception>();


        foreach (var item in this.Values.OfType<IDisposable>())

        {

            try

            {

                item.Dispose();

            }

            catch (Exception e)

            {

                exceptions.Add(e);

            }

        }


        base.Dispose(disposing);


        if (exceptions.Any()) throw new AggregateException(exceptions);

    }

}



// 사용

using (var connection = DisposableThreadLocal.Create(() => { var conn = new SqlConnection("접속문자열"); conn.Open(); return conn; }))

{

    Parallel.For(1, 1000, x =>

    {

        var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper

    });

}





출처: http://neue.cc/2013/03/09_400.html 

저작자 표시
신고
by 흥배 2016.11.04 08:00
| 1 |