비동기 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에 대한 차단 지연없이 결과를 사용할 준비가되면 콜백 알림이있는 것처럼 작동합니다.
LINQ에는 실제로이 기능이 없지만 프레임 워크 자체는 수행합니다 ... 자신의 비동기 쿼리 실행기를 30 줄 정도 쉽게 롤링 할 수 있습니다.
편집 : 이것을 작성함으로써 나는 왜 그것을 구현하지 않았는지 발견했습니다. 익명 유형은 로컬 범위이므로 처리 할 수 없습니다. 따라서 콜백 함수를 정의 할 방법이 없습니다. 이것은 많은 linq to sql 항목이 select 절에서 생성하기 때문에 매우 중요한 것입니다. 아래 제안 중 어느 것도 동일한 운명을 겪고 있으므로 여전히 사용하기 가장 쉬운 방법이라고 생각합니다!
편집 : 유일한 해결책은 익명 유형을 사용하지 않는 것입니다. 콜백을 IEnumerable (유형 인수 없음)로만 선언하고 리플렉션을 사용하여 필드에 액세스 할 수 있습니다 (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;
}
}
}
Going to go put this up on my blog now, pretty handy.
TheSoftwareJedi's and ulrikb's(aka user316318) solutions are good for any LINQ type, but (as pointed by Chris Moschini) do NOT delegating to underlying asynchronous calls that leverage Windows I/O Completion Ports.
Wesley Bakker's Asynchronous DataContext post (triggered by a blog post of Scott Hanselman ) describe class for LINQ to SQL that uses sqlCommand.BeginExecuteReader/sqlCommand.EndExecuteReader, which leverage Windows I/O Completion Ports.
I/O completion ports provide an efficient threading model for processing multiple asynchronous I/O requests on a multiprocessor system.
Based on Michael Freidgeim's answer and mentioned blog post from Scott Hansellman and fact that you can use async
/await
, you can implement reusable ExecuteAsync<T>(...)
method, which executes underlying SqlCommand
asynchronously:
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);
}
And then you can (re)use it like this:
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);
}
}
}
I started a simple github project named Asynq to do asynchronous LINQ-to-SQL query execution. The idea is quite simple albeit "brittle" at this stage (as of 8/16/2011):
- Let LINQ-to-SQL do the "heavy" work of translating your
IQueryable
into aDbCommand
via theDataContext.GetCommand()
. - For SQL 200[058], cast up from the abstract
DbCommand
instance you got fromGetCommand()
to get aSqlCommand
. If you're using SQL CE you're out of luck sinceSqlCeCommand
does not expose the async pattern forBeginExecuteReader
andEndExecuteReader
. - Use
BeginExecuteReader
andEndExecuteReader
off theSqlCommand
using the standard .NET framework asynchronous I/O pattern to get yourself aDbDataReader
in the completion callback delegate that you pass to theBeginExecuteReader
method. - Now we have a
DbDataReader
which we have no idea what columns it contains nor how to map those values back up to theIQueryable
'sElementType
(most likely to be an anonymous type in the case of joins). Sure, at this point you could hand-write your own column mapper that materializes its results back into your anonymous type or whatever. You'd have to write a new one per each query result type, depending on how LINQ-to-SQL treats your IQueryable and what SQL code it generates. This is a pretty nasty option and I don't recommend it since it's not maintainable nor would it be always correct. LINQ-to-SQL can change your query form depending on the parameter values you pass in, for examplequery.Take(10).Skip(0)
produces different SQL thanquery.Take(10).Skip(10)
, and perhaps a different resultset schema. Your best bet is to handle this materialization problem programmatically: - "Re-implement" a simplistic runtime object materializer that pulls columns off the
DbDataReader
in a defined order according to the LINQ-to-SQL mapping attributes of theElementType
Type for theIQueryable
. Implementing this correctly is probably the most challenging part of this solution.
As others have discovered, the DataContext.Translate()
method does not handle anonymous types and can only map a DbDataReader
directly to a properly attributed LINQ-to-SQL proxy object. Since most queries worth writing in LINQ are going to involve complex joins which inevitably end up requiring anonymous types for the final select clause, it's pretty pointless to use this provided watered-down DataContext.Translate()
method anyway.
There are a few minor drawbacks to this solution when leveraging the existing mature LINQ-to-SQL IQueryable provider:
- You cannot map a single object instance to multiple anonymous type properties in the final select clause of your
IQueryable
, e.g.from x in db.Table1 select new { a = x, b = x }
. LINQ-to-SQL internally keeps track of which column ordinals map to which properties; it does not expose this information to the end user so you have no idea which columns in theDbDataReader
are reused and which are "distinct". - You cannot include constant values in your final select clause - these do not get translated into SQL and will be absent from the
DbDataReader
so you'd have to build custom logic to pull these constant values up from theIQueryable
'sExpression
tree, which would be quite a hassle and is simply not justifiable.
I'm sure there are other query patterns that might break but these are the two biggest I could think of that could cause problems in an existing LINQ-to-SQL data access layer.
These problems are easy to defeat - simply don't do them in your queries since neither pattern provides any benefit to the end result of the query. Hopefully this advice applies to all query patterns that would potentially cause object materialization problems :-P. It's a hard problem to solve not having access to LINQ-to-SQL's column mapping information.
A more "complete" approach to solving the problem would be to effectively re-implement nearly all of LINQ-to-SQL, which is a bit more time-consuming :-P. Starting from a quality, open-source LINQ-to-SQL provider implementation would be a good way to go here. The reason you'd need to reimplement it is so that you'd have access to all of the column mapping information used to materialize the DbDataReader
results back up to an object instance without any loss of information.
참고URL : https://stackoverflow.com/questions/252355/how-to-write-asynchronous-linq-query
'programing tip' 카테고리의 다른 글
완료 버튼을 키보드에 추가하는 방법은 무엇입니까? (0) | 2020.12.14 |
---|---|
모든 종속성 및 패키지를 디렉터리에 다운로드하는 방법 (0) | 2020.12.14 |
로컬 Git 저장소를 다른 컴퓨터로 푸시하는 방법은 무엇입니까? (0) | 2020.12.13 |
ggplot에서 스트립 라벨의 위치를 위에서 아래로 변경할 수 있습니까? (0) | 2020.12.13 |
맞춤 Google Now 카드 만들기 (0) | 2020.12.13 |