Как написать асинхронный запрос LINQ?
-
05-07-2019 - |
Вопрос
После прочтения нескольких связанных с 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? Это работает как обратный вызов уведомление о том, что результаты готовы к использованию без задержки блокировки ввода / вывода.
Решение
Хотя LINQ на самом деле этого не имеет, сама структура делает это ... Вы можете легко свернуть своего собственного исполнителя асинхронных запросов в 30 строк или около того ... На самом деле, я просто собрал это вместе для вас :) р>
РЕДАКТИРОВАТЬ: написав это, я понял, почему они не реализовали это. Он не может обрабатывать анонимные типы, поскольку они имеют локальную область видимости. Таким образом, у вас нет возможности определить функцию обратного вызова. Это довольно важная вещь, так как многие вещи из linq to sql создают их в предложении select. Любое из приведенных ниже предложений постигнет та же участь, поэтому я все еще думаю, что это самое простое в использовании!
РЕДАКТИРОВАТЬ. Единственное решение - не использовать анонимные типы. Вы можете объявить обратный вызов как просто принимающий IEnumerable (без аргументов типа), и использовать отражение для доступа к полям (ICK !!). Другим способом было бы объявить обратный вызов как & Quot; динамический & Quot; ... о ... подождите ... Это еще не вышло. :) Это еще один достойный пример того, как можно использовать динамический. Некоторые могут назвать это злоупотреблением.
Добавьте это в свою библиотеку утилит:
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;
}
}
}
Собираюсь пойти и разместить это в моем блоге, очень удобно.
Другие советы
TheSoftwareJedi's и ulrikb (aka user316318) решения хороши для любого типа LINQ, но (как указывает Крис Москини ) НЕ делегируют лежащие в основе асинхронные вызовы, использующие порты завершения ввода-вывода Windows.
пост асинхронного DataContext Уэсли Баккера ( запускается публикацией в блоге Скотта Хансельмана ), описывающей класс для LINQ to SQL, использующей sqlCom. BeginExecuteReader / sqlCommand.EndExecuteReader, которые используют порты завершения ввода-вывода Windows. Р>
Порты завершения ввода / вывода обеспечить эффективную модель потоков для обработки нескольких асинхронных запросов ввода / вывода в многопроцессорной системе. Р>
Основано на ответе Майкла Фрейдгейма и упоминается запись в блоге Скотта Ханселмана и тот факт, что вы можете использовать async
/ await
, вы можете реализовать метод многократного использования ExecuteAsync<T>(...)
, который выполняет базовый 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);
}
И тогда вы можете (повторно) использовать его следующим образом:
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);
}
}
}
Я запустил простой проект github с именем Asynq для выполнения асинхронного выполнения запросов LINQ-to-SQL. Идея довольно проста, хотя & Quot; хрупкая & Quot; на данном этапе (по состоянию на 16.08.2011 г.):
<Ол>IQueryable
в DbCommand
через DataContext.GetCommand()
. GetCommand()
, полученного из SqlCommand
, чтобы получить SqlCeCommand
. Если вы используете SQL CE, вам не повезло, поскольку BeginExecuteReader
не предоставляет асинхронный шаблон для EndExecuteReader
и DbDataReader
. ElementType
и query.Take(10).Skip(0)
отключите query.Take(10).Skip(10)
с помощью стандартного шаблона асинхронного ввода-вывода Framework .NET, чтобы получить DataContext.Translate()
делегата завершения обратного вызова, который вы передаете методу from x in db.Table1 select new { a = x, b = x }
. Expression
, который мы не знаем, какие столбцы он содержит, и как сопоставить эти значения обратно с <=> <=> (скорее всего, это будет анонимный тип в случае объединений). ). Конечно, в этот момент вы можете написать свой собственный картограф, который материализует его результаты обратно в ваш анонимный тип или что-то еще. Вы должны будете написать новый для каждого типа результата запроса, в зависимости от того, как LINQ-to-SQL обрабатывает ваш IQueryable и какой код SQL он генерирует. Это довольно неприятный вариант, и я не рекомендую его, поскольку он не подлежит ремонту и не всегда будет правильным. LINQ-to-SQL может изменить форму запроса в зависимости от значений параметров, которые вы передаете, например, <=> генерирует другой SQL, чем <=>, и, возможно, другую схему набора результатов. Лучше всего решить проблему материализации программно: Как обнаружили другие, метод <=> не обрабатывает анонимные типы и может только сопоставлять <=> непосредственно с правильно назначенным прокси-объектом LINQ-to-SQL. Поскольку большинство запросов, заслуживающих написания в LINQ, будут включать сложные объединения, которые неизбежно приведут к необходимости анонимных типов для заключительного предложения select, использовать этот предоставленный расширенный метод <=> в любом случае довольно бессмысленно.
При использовании существующего зрелого поставщика LINQ-to-SQL IQueryable у этого решения есть несколько незначительных недостатков:
<Ол>Я уверен, что есть и другие шаблоны запросов, которые могут сломаться, но это два самых больших, о которых я мог подумать, что может вызвать проблемы в существующем уровне доступа к данным LINQ-to-SQL.
Эти проблемы легко преодолеть - просто не делайте их в своих запросах, поскольку ни один из шаблонов не дает никакого преимущества для конечного результата запроса. Надеемся, что этот совет применим ко всем шаблонам запросов, которые потенциально могут вызвать проблемы с материализацией объектов: Трудно решить проблему отсутствия доступа к информации о сопоставлении столбцов в LINQ-to-SQL.
Еще & "завершено"! " подход к решению проблемы заключается в эффективном повторном внедрении почти всего LINQ-to-SQL, что требует немного больше времени :-P. Начнем с качественной реализации поставщика LINQ-to-SQL с открытым исходным кодом.иди сюда. Причина, по которой вам нужно переопределить это, заключается в том, что у вас будет доступ ко всей информации об отображении столбцов, используемой для материализации результатов <=> обратно в экземпляр объекта без потери информации.