Вопрос

После прочтения нескольких связанных с 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 г.):

<Ол>
  • Пусть LINQ-to-SQL выполняет " heavy " работа по переводу вашего IQueryable в DbCommand через DataContext.GetCommand().
  • Для SQL 200 [058] приведите из абстрактного экземпляра 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, чем <=>, и, возможно, другую схему набора результатов. Лучше всего решить проблему материализации программно:
  • &
  • Quot; Re орудию Quot &; упрощенный инструментализатор объектов времени выполнения, который извлекает столбцы из <=> в определенном порядке в соответствии с атрибутами отображения LINQ-to-SQL типа <=> для <=>. Реализация этого правильно, вероятно, самая сложная часть этого решения.
  • Как обнаружили другие, метод <=> не обрабатывает анонимные типы и может только сопоставлять <=> непосредственно с правильно назначенным прокси-объектом LINQ-to-SQL. Поскольку большинство запросов, заслуживающих написания в LINQ, будут включать сложные объединения, которые неизбежно приведут к необходимости анонимных типов для заключительного предложения select, использовать этот предоставленный расширенный метод <=> в любом случае довольно бессмысленно.

    При использовании существующего зрелого поставщика LINQ-to-SQL IQueryable у этого решения есть несколько незначительных недостатков:

    <Ол>
  • Вы не можете сопоставить один экземпляр объекта нескольким свойствам анонимного типа в последнем предложении select вашего <=>, например. <=>. LINQ-to-SQL внутренне отслеживает, какие порядковые номера столбцов соответствуют каким свойствам; она не предоставляет эту информацию конечному пользователю, поэтому вы не знаете, какие столбцы в <=> используются повторно, а какие " различаются ".
  • Вы не можете включать константные значения в окончательное предложение select - они не переводятся в SQL и будут отсутствовать в <=>, поэтому вам нужно будет создать собственную логику для извлечения этих константных значений из <=> <=> дерево, которое было бы довольно хлопотно и просто неоправданно.
  • Я уверен, что есть и другие шаблоны запросов, которые могут сломаться, но это два самых больших, о которых я мог подумать, что может вызвать проблемы в существующем уровне доступа к данным LINQ-to-SQL.

    Эти проблемы легко преодолеть - просто не делайте их в своих запросах, поскольку ни один из шаблонов не дает никакого преимущества для конечного результата запроса. Надеемся, что этот совет применим ко всем шаблонам запросов, которые потенциально могут вызвать проблемы с материализацией объектов: Трудно решить проблему отсутствия доступа к информации о сопоставлении столбцов в LINQ-to-SQL.

    Еще & "завершено"! " подход к решению проблемы заключается в эффективном повторном внедрении почти всего LINQ-to-SQL, что требует немного больше времени :-P. Начнем с качественной реализации поставщика LINQ-to-SQL с открытым исходным кодом.иди сюда. Причина, по которой вам нужно переопределить это, заключается в том, что у вас будет доступ ко всей информации об отображении столбцов, используемой для материализации результатов <=> обратно в экземпляр объекта без потери информации.

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top