2008-10-31 6 views
56

많은 LINQ 관련 자료를 읽은 후에 비동기 LINQ 쿼리를 작성하는 방법을 소개 한 기사가 없다는 사실을 갑자기 깨달았습니다.비동기 LINQ 쿼리를 작성하는 방법은 무엇입니까?

LINQ to SQL을 사용한다고 가정 해 봅니다. 그러나 SQL 데이터베이스가 느리게 응답하면이 코드 블록을 사용하는 스레드가 방해 받게됩니다.

var result = from item in Products where item.Price > 3 select item.Name; 
foreach (var name in result) 
{ 
    Console.WriteLine(name); 
} 

현재 LINQ 쿼리 사양이 지원하지 않는다고합니다.

비동기 프로그래밍 LINQ를 수행 할 수있는 방법이 있습니까? I/O에서 차단 지연없이 결과를 사용할 준비가되면 콜백 알림이있는 것처럼 작동합니다.

+0

당신을위한 확인 작업 밖으로 아래의 대답을 했습니까? – TheSoftwareJedi

+0

http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx에서 Reactive Extensions for .NET을 확인하십시오.이 기능은 비동기 Linq 쿼리 용으로 설계되었습니다. –

+2

실제로 Linq *와 SQL * 질의는 작성자가 묻는 것입니다. 여기 –

답변

34

LINQ가 실제로이 기능을 갖고 있지는 않지만 프레임 워크 자체는 ... 30 개의 라인으로 나만의 비동기 쿼리 실행 프로그램을 쉽게 롤백 할 수 있습니다 ... 사실,)

편집 :이 글을 쓰면서 내가 구현하지 않은 이유를 발견했습니다. 익명 형식은 로컬 범위이므로 익명 형식을 처리 할 수 ​​없습니다. 따라서 콜백 함수를 정의 할 방법이 없습니다. 이것은 많은 linq에서 sql에 이르기까지 select 절에서 생성됩니다. 아래 제안 중 하나라도 같은 운명을 겪습니다. 그래서 나는이 것이 가장 사용하기 쉽다고 생각합니다!

편집 : 유일한 해결책은 익명 형식을 사용하지 않는 것입니다. 콜백은 IEnumerable (형식 args 없음)을 사용하는 것으로 선언 할 수 있으며 리플렉션을 사용하여 필드 (ICK !!)에 액세스 할 수 있습니다. 또 다른 방법은 콜백을 "동적 인"것으로 선언하는 것입니다 ... 오 ... 기다려 ... 아직 안 끝났어. :) 이것은 동적 인 방법을 사용할 수있는 또 다른 예입니다. 일부는 학대라고 부를 수도 있습니다. 당신의 유틸리티 라이브러리에

던져이 :

public static class AsynchronousQueryExecutor 
{ 
    public static void Call<T>(IEnumerable<T> query, Action<IEnumerable<T>> callback, Action<Exception> errorCallback) 
    { 
     Func<IEnumerable<T>, IEnumerable<T>> func = 
      new Func<IEnumerable<T>, IEnumerable<T>>(InnerEnumerate<T>); 
     IEnumerable<T> result = null; 
     IAsyncResult ar = func.BeginInvoke(
          query, 
          new AsyncCallback(delegate(IAsyncResult arr) 
          { 
           try 
           { 
            result = ((Func<IEnumerable<T>, IEnumerable<T>>)((AsyncResult)arr).AsyncDelegate).EndInvoke(arr); 
           } 
           catch (Exception ex) 
           { 
            if (errorCallback != null) 
            { 
             errorCallback(ex); 
            } 
            return; 
           } 
           //errors from inside here are the callbacks problem 
           //I think it would be confusing to report them 
           callback(result); 
          }), 
          null); 
    } 
    private static IEnumerable<T> InnerEnumerate<T>(IEnumerable<T> query) 
    { 
     foreach (var item in query) //the method hangs here while the query executes 
     { 
      yield return item; 
     } 
    } 
} 

그리고 당신이처럼 사용할 수 있습니다 매우 편리합니다, 지금 내 블로그에 올려 갈

class Program 
{ 

    public static void Main(string[] args) 
    { 
     //this could be your linq query 
     var qry = TestSlowLoadingEnumerable(); 

     //We begin the call and give it our callback delegate 
     //and a delegate to an error handler 
     AsynchronousQueryExecutor.Call(qry, HandleResults, HandleError); 

     Console.WriteLine("Call began on seperate thread, execution continued"); 
     Console.ReadLine(); 
    } 

    public static void HandleResults(IEnumerable<int> results) 
    { 
     //the results are available in here 
     foreach (var item in results) 
     { 
      Console.WriteLine(item); 
     } 
    } 

    public static void HandleError(Exception ex) 
    { 
     Console.WriteLine("error"); 
    } 

    //just a sample lazy loading enumerable 
    public static IEnumerable<int> TestSlowLoadingEnumerable() 
    { 
     Thread.Sleep(5000); 
     foreach (var i in new int[] { 1, 2, 3, 4, 5, 6 }) 
     { 
      yield return i; 
     } 
    } 

} 

.

+0

어떤 종류의 주조를 통합 할 수 없습니까? 이 링크처럼 : http://tomasp.net/articles/cannot-return-anonymous-type-from-method.aspx. –

+3

효과적으로 블록을 스레드 풀의 다른 스레드로 전달하고 있습니까? 아니면 여기에 어떤 마법이 있습니까? 동기 코드 대신 기본 비동기에 위임의 상단에있는 IAsync 패턴을 적용하는 것이 어떠한 스레드를 차단하지 그래서 활용 윈도우 I/O 완료 포트가 꺼져 대기 시간을 손에 있음을 호출 - –

+22

이 닷넷의 일반적인 실수이다. Harry가 지적했듯이이 블록을 다른 ThreadPool 스레드에 전달합니다. 이것은 시작 스레드가 다른 쪽 작업을 자유롭게 수행 할 수 있다는 작은 장점 만 있습니다. 이 방법은 긴 쿼리에서 충분한 동시 요청이 들어 오면 모든 ThreadPool 스레드를 차단할 위험이 있습니다. –

12

TheSoftwareJedi 년대와 ulrikb 's이 (일명 user316318) 솔루션은 LINQ 유형에 대한 좋은,하지만 (Chris Moschini에 의해 지적) 기본 비동기에 위임하지 마십시오 활용하는 윈도우 I/O 완료 포트를 호출합니다. (a blog post of Scott Hanselman에 의해 트리거)

웨슬리 바커의 Asynchronous DataContext 포스트는 sqlCommand.BeginExecuteReader/sqlCommand.EndExecuteReader, 활용의 Windows I/O 완료 포트를 사용하는 SQL에 LINQ에 대한 클래스를 설명합니다.

I/O completion ports은 다중 프로세서 시스템에서 다중 비동기 I/O 요청을 처리하기위한 효율적인 스레딩 모델을 제공합니다.

+0

이를 adressing 아무것도 표시되지 않습니다 작업이 완료 될 때까지 대기하는 스레드를 사용하지 않아야합니다. –

3

비동기 LINQ-to-SQL 쿼리를 실행하기 위해 Asynq이라는 간단한 github 프로젝트를 시작했습니다. 아이디어는 (2011년 8월 16일 기준)이 단계에서 "취성"이기는하지만 매우 간단합니다 :

  1. 합시다 LINQ - 투 - SQL 할를 A DbCommandIQueryableDataContext.GetCommand()을 통해 번역의 "무거운"작업 .
  2. SQL 200 [058]의 경우 GetCommand()에서 가져온 DbCommand 인스턴스에서 캐스팅하여 SqlCommand이됩니다. SqlCeCommand부터 BeginExecuteReaderEndExecuteReader의 비동기 패턴을 노출시키지 않으므로 SQL CE를 사용하고 있다면 운이 없으면됩니다.
  3. 표준 .NET 프레임 워크 비동기 I/O 패턴을 사용하여 SqlCommand에서 BeginExecuteReaderEndExecuteReader을 사용하면 메서드로 전달되는 완료 콜백 대리자에서 DbDataReader이됩니다.
  4. 이제 DbDataReader에 어떤 열이 포함되어 있는지 알지 못하며 해당 값을 IQueryableElementType (대부분 조인의 경우 익명 형식 일 가능성이 높음)까지 매핑하는 방법도 없습니다. 물론,이 시점에서 익명 형식으로 결과를 구체화하는 열 매퍼를 직접 작성할 수 있습니다. LINQ-to-SQL이 IQueryable을 처리하는 방법과 생성하는 SQL 코드에 따라 각 쿼리 결과 유형마다 새 쿼리를 작성해야합니다. 이것은 매우 불쾌한 옵션이며 유지 보수가 불가능하거나 항상 올바를 것이므로 권장하지 않습니다. LINQ-to-SQL은 전달한 매개 변수 값에 따라 쿼리 양식을 변경할 수 있습니다. 예를 들어 query.Take(10).Skip(0)query.Take(10).Skip(10)이 아닌 다른 결과 집합과 다른 결과 집합 스키마를 생성합니다. 최선의 방법은이 구체화 문제를 프로그래밍 방식으로 처리하는 것입니다.
  5. ElementType의 LINQ-SQL 매핑 특성에 따라 DbDataReader의 열을 정의 된 순서로 가져 오는 단순한 런타임 개체 구체화를 "다시 구현"합니다. IQueryable. 이를 올바르게 구현하는 것이이 솔루션에서 가장 어려운 부분 일 것입니다. 다른 사람이 발견 한 것처럼

DataContext.Translate() 방법은 익명 형식을 처리하지 않습니다 만 제대로 기인 LINQ - 투 - SQL 프록시 개체에 직접 DbDataReader을 매핑 할 수 있습니다. LINQ에서 작성할 가치가있는 대부분의 쿼리는 필연적으로 최종 select 절에 익명 형식을 요구하는 복잡한 조인을 포함 할 것이기 때문에 제공된 weedred-down DataContext.Translate() 메서드를 사용하는 것은 무의미합니다.

  1. 당신은의 최종 선택 절에 여러 익명 형식 속성에 하나의 개체 인스턴스를 매핑 할 수 없습니다 :

    은 기존의 성숙 LINQ - 투 - SQL 된 IQueryable 제공자를 활용하여이 솔루션에 대한 몇 가지 사소한 단점이 있습니다 IQueryable, 예 from x in db.Table1 select new { a = x, b = x }. LINQ-to-SQL은 내부적으로 어떤 열 서수가 어떤 속성에 매핑되는지 추적합니다. 이 정보를 최종 사용자에게 공개하지 않으므로 DbDataReader의 어떤 열이 재사용되고 "별개"인지 알 수 없습니다.

  2. 최종 선택 절에 상수 값을 포함 할 수 없습니다.이 값은 SQL로 변환되지 않고 DbDataReader에 없습니다.이 상수 값을 IQueryableExpression 트리에서 끌어 올 사용자 지정 논리를 작성해야합니다. 이것은 상당히 번거롭고 단순히 정당화 될 수없는 것이다.

나는 깨질 수있는 다른 쿼리 패턴이 있지만이 두 가지가 기존의 LINQ-to-SQL 데이터 액세스 계층에서 문제를 일으킬 수 있다고 생각할 수 있다고 생각합니다.

이러한 문제는 쉽게 해결할 수 있습니다. 두 패턴 모두 쿼리의 최종 결과에 아무런 이점도주지 않으므로 쿼리에서이를 수행하지 마십시오. 다행히도이 조언은 객체 구체화 문제를 일으킬 수있는 모든 쿼리 패턴에 적용됩니다.LINQ-to-SQL의 열 매핑 정보에 대한 액세스 권한이없는 문제를 해결하는 것은 어려운 문제입니다.

문제를 해결하기보다 "완전한"접근 방식은 효율적으로 거의 모든의를 다시 구현하는 것입니다 LINQ - 투 - SQL이다, 조금 더 시간이 소요 - P를. 품질이 우수한 오픈 소스 LINQ-to-SQL 공급자 구현에서 시작하는 것이 여기에 좋은 방법입니다. 다시 구현해야하는 이유는 정보 손실없이 DbDataReader 결과를 구체화하기 위해 사용 된 모든 열 매핑 정보에 대한 액세스 권한을 객체 인스턴스에 백업 할 수 있기 때문입니다. 처럼 사용

protected static async Task<IEnumerable<T>> ExecuteAsync<T>(IQueryable<T> query, 
    DataContext ctx, 
    CancellationToken token = default(CancellationToken)) 
{ 
    var cmd = (SqlCommand)ctx.GetCommand(query); 

    if (cmd.Connection.State == ConnectionState.Closed) 
     await cmd.Connection.OpenAsync(token); 
    var reader = await cmd.ExecuteReaderAsync(token); 

    return ctx.Translate<T>(reader); 
} 

그리고 당신은 (재) 할 수 있습니다

+1

이 github 프로젝트는 매우 많은 작업 진행 중임을 잊어 버리십시오. 나는 모든 생산 코드에서 그것을 사용하는 것을 적극적으로 권유합니다. 그것은이 단계에서 간단한 연구 프로젝트가 될 수 있다는 것을 보여주기 위해 사용되었습니다. –

+0

비슷한 해결책을 가진 코드 예제. http://www.hanselman.com/blog/TheWeeklySourceCode51AsynchronousDatabaseAccessAndLINQToSQLFun.aspx –

4

Michael Freidgeim's answer을 기반으로하고 blog post from Scott Hansellman하고 async/await을 사용할 수 있다는 사실을 언급, 당신은 비동기 SqlCommand를 기본 실행한다 재사용 ExecuteAsync<T>(...) 방법을 구현할 수 있습니다 이 :

public async Task WriteNamesToConsoleAsync(string connectionString, CancellationToken token = default(CancellationToken)) 
{ 
    using (var ctx = new DataContext(connectionString)) 
    { 
     var query = from item in Products where item.Price > 3 select item.Name; 
     var result = await ExecuteAsync(query, ctx, token); 
     foreach (var name in result) 
     { 
      Console.WriteLine(name); 
     } 
    } 
} 
+0

이 지금까지 개체를 쿼리하는 깨끗한 솔루션입니다,이 시나리오의 대부분을 커버하고 멋지다입니다. 그러나 비동기 SubmitChanges를 달성 할 수있는 방법이 있습니까? 예를 들어, 테이블에 새 레코드를 삽입합니다. – ogggre