在我读了一堆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 :(得分:35)
虽然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;
}
}
}
现在就把它放在我的博客上,非常方便。
答案 1 :(得分:15)
TheSoftwareJedi和ulrikb(又名user316318)解决方案适用于任何LINQ类型,但(由Chris Moschini指出)不委托利用Windows I / O完成端口的底层异步调用
Wesley Bakker的Asynchronous DataContext帖子(由a blog post of Scott Hanselman触发)描述了使用sqlCommand.BeginExecuteReader / sqlCommand.EndExecuteReader的LINQ to SQL类,它利用了Windows I / O完成端口。
I/O completion ports提供了一种有效的线程模型,用于处理多处理器系统上的多个异步I / O请求。
答案 2 :(得分:5)
基于Michael Freidgeim's answer并提及blog post from Scott Hansellman以及您可以使用async
/ await
这一事实,您可以实施可重复使用的ExecuteAsync<T>(...)
方法,该方法执行基础{ {1}}异步:
SqlCommand
然后你可以(重新)使用它:
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);
}
答案 3 :(得分:4)
我启动了一个名为Asynq的简单github项目来执行异步LINQ-to-SQL查询。这个想法很简单,虽然在这个阶段是“脆弱的”(截至2011年8月16日):
IQueryable
完成将DbCommand
翻译成DataContext.GetCommand()
的“繁重”工作。DbCommand
获得的抽象GetCommand()
实例中获取SqlCommand
。如果你使用的是SQL CE,那么你就不幸了,因为SqlCeCommand
没有公开BeginExecuteReader
和EndExecuteReader
的异步模式。BeginExecuteReader
使用EndExecuteReader
和SqlCommand
,以便在您传递的完成回调委托中获得DbDataReader
到BeginExecuteReader
方法。DbDataReader
我们不知道它包含哪些列,也不知道如何将这些值映射回IQueryable
的{{1}}(最有可能是匿名的)在连接的情况下键入)。当然,此时您可以手工编写自己的列映射器,将其结果具体化为您的匿名类型或其他类型。您必须为每个查询结果类型编写一个新的,具体取决于LINQ-to-SQL如何处理您的IQueryable以及它生成的SQL代码。这是一个非常讨厌的选项,我不推荐它,因为它不可维护,也不总是正确的。 LINQ-to-SQL可以根据您传入的参数值更改查询表单,例如ElementType
生成的SQL不同于query.Take(10).Skip(0)
,也可能是不同的结果集架构。您最好的选择是以编程方式处理此实现问题:query.Take(10).Skip(10)
类型的LINQ-to-SQL映射属性,以定义的顺序从DbDataReader
中拉出列。 }}。正确实现这可能是此解决方案中最具挑战性的部分。正如其他人发现的那样,ElementType
方法不处理匿名类型,只能将IQueryable
直接映射到正确归属的LINQ-to-SQL代理对象。由于大多数值得在LINQ中编写的查询都涉及复杂的连接,这最终不可避免地需要最终select子句的匿名类型,所以无论如何使用这个提供的淡化DataContext.Translate()
方法都是毫无意义的。
在利用现有成熟的LINQ-to-SQL IQueryable提供程序时,此解决方案存在一些小缺点:
DbDataReader
的最终选择条款中将单个对象实例映射到多个匿名类型属性,例如DataContext.Translate()
。 LINQ-to-SQL在内部跟踪哪些列序列映射到哪些属性;它不会将此信息公开给最终用户,因此您不知道IQueryable
中的哪些列被重用以及哪些列是“不同的”。from x in db.Table1 select new { a = x, b = x }
中,因此您必须构建自定义逻辑以从{中提取这些常量值{1}} DbDataReader
树,这将是一件非常麻烦的事情,根本就没有道理。我确信还有其他查询模式可能会中断,但这些是我能想到的最大可能导致现有LINQ-to-SQL数据访问层出现问题的两种模式。
这些问题很容易被打败 - 只是不要在查询中执行这些问题,因为这两种模式都不会对查询的最终结果产生任何好处。希望这个建议适用于所有可能导致对象实现问题的查询模式:-P。解决不能访问LINQ-to-SQL的列映射信息是一个难题。
解决问题的一种更“完整”的方法是有效地重新实现几乎所有LINQ-to-SQL,这有点耗时:-P。从高质量的开源LINQ-to-SQL提供程序实现开始将是一个很好的方法。您需要重新实现它的原因是,您可以访问用于实现DbDataReader
结果的所有列映射信息,并将其备份到对象实例,而不会丢失任何信息。