質問

多数の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 !!)。別の方法は、コールバックを<!> quot; dynamic <!> quot;として宣言することです。...ああ...待って...まだ出ていません。 :)これは、動的の使用方法のもう1つの適切な例です。悪用と呼ばれる人もいます。

ユーティリティライブラリにこれを投げます:

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および ulrikb (別名user316318)ソリューションはどのLINQタイプにも適していますが、( Chris Moschini が示すように)委任しないでくださいWindows I / O完了ポートを活用する基礎となる非同期呼び出し。

Wesley Bakkerの Asynchronous DataContext の投稿( Scott Hanselmanのブログ投稿によってトリガーされます)sqlCommandを使用するLINQ to SQLのクラスについて説明します。 BeginExecuteReader /sqlCommand.EndExecuteReader。WindowsI / O完了ポートを活用します。

I / O完了ポートマルチプロセッサシステムで複数の非同期I / O要求を処理するための効率的なスレッドモデルを提供します。

Michael Freidgeimの回答および言及された Scott Hansellmanのブログ投稿と、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);
        }
    }
}

Asynq という名前の単純なgithubプロジェクトを開始して、非同期LINQ-to-SQLクエリを実行しました。アイデアは<!> quot; brittle <!> quot;とはいえ非常に単純です。この段階(2011年8月16日現在):

  1. LINQ-to-SQLに<!> quot; heavy <!> quot;を実行させます。 IQueryableを介してDbCommandDataContext.GetCommand()に変換する作業。
  2. SQL 200 [058]の場合、GetCommand()から取得した抽象SqlCommandインスタンスからキャストアップしてSqlCeCommandを取得します。 SQL CEを使用している場合、BeginExecuteReaderEndExecuteReaderおよびDbDataReaderの非同期パターンを公開しないため、運が悪いです。
  3. 標準の.NETフレームワーク非同期I / Oパターンを使用してElementTypeからquery.Take(10).Skip(0)およびquery.Take(10).Skip(10)を使用して、DataContext.Translate()メソッドに渡す完了コールバックデリゲートでfrom x in db.Table1 select new { a = x, b = x }を取得します。
  4. 現在、Expressionがあり、その列に含まれる列がわからず、それらの値を<=>の<=>にマップする方法もわかりません(結合の場合、ほとんどの場合匿名型です) )。もちろん、この時点で、その結果を匿名型などに具体化する独自の列マッパーを手書きで書くことができます。 LINQ-to-SQLがIQueryableを処理する方法と生成するSQLコードに応じて、クエリ結果タイプごとに新しいものを作成する必要があります。これはかなり厄介なオプションであり、保守可能ではなく、常に正しいとは限らないため、お勧めしません。 LINQ-to-SQLは、渡すパラメーター値に応じてクエリフォームを変更できます。たとえば、<=>は<=>とは異なるSQLを生成し、おそらく異なる結果セットスキーマを生成します。あなたの最善の策は、この実体化問題をプログラムで処理することです:
  5. <!> quot;再実装<!> quot; <=>の<=>タイプのLINQ-to-SQLマッピング属性に従って、定義された順序で<=>から列を取り出す単純なランタイムオブジェクトマテリアライザー。これを正しく実装することは、おそらくこのソリューションの最も難しい部分です。

他の人が発見したように、<=>メソッドは匿名型を処理せず、<=>を適切に属性付けされたLINQ-to-SQLプロキシオブジェクトに直接マップすることしかできません。 LINQで記述する価値のあるクエリのほとんどは、最終的なselect句に匿名型を必然的に必要とする複雑な結合を伴うため、この提供された簡略化された<=>メソッドを使用するのはかなり無意味です。

既存の成熟したLINQ-to-SQL IQueryableプロバイダーを活用する場合、このソリューションにはいくつかの小さな欠点があります:

  1. 単一のオブジェクトインスタンスを、<=>の最後のselect句の複数の匿名型プロパティにマッピングすることはできません。 <=>。 LINQ-to-SQLは、どの列序数がどのプロパティにマップされているかを内部的に追跡します。この情報はエンドユーザーに公開されないため、<=>のどの列が再利用され、どの列が<!> quot; distinct <!> quot;であるかはわかりません。
  2. 最終選択句に定数値を含めることはできません-これらはSQLに変換されず、<=>には存在しないため、これらの定数値を<=>からプルアップするカスタムロジックを構築する必要がありますの<=>ツリー。これは非常に面倒で、単に正当化することはできません。

壊れる可能性のある他のクエリパターンがあるはずですが、これらは既存のLINQ-to-SQLデータアクセスレイヤーで問題を引き起こす可能性があると考えられる最大の2つです。

これらの問題は簡単に解決できます。どちらのパターンもクエリの最終結果にメリットをもたらさないため、クエリでは単純に実行しないでください。このアドバイスが、オブジェクトの実体化の問題を引き起こす可能性のあるすべてのクエリパターンに適用されることを願っています。 LINQ-to-SQLの列マッピング情報にアクセスできないことを解決するのは難しい問題です。

さらに<!> quot; complete <!> quot;問題を解決するためのアプローチは、LINQ-to-SQLのほぼすべてを効果的に再実装することです。これはもう少し時間がかかります:-P。品質から始めると、オープンソースのLINQ-to-SQLプロバイダーの実装が良いでしょうここに行くにはy。再実装する必要があるのは、情報を失うことなく<=>結果をオブジェクトインスタンスにマテリアライズするために使用されるすべての列マッピング情報にアクセスできるようにするためです。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top