64

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のブロック遅延なしに結果を使用する準備ができたときに、コールバック通知があるように機能します。

4

4 に答える 4

38

LINQ自体には実際にはこれがありませんが、フレームワーク自体にはあります...独自の非同期クエリエグゼキュータを30行程度で簡単にロールできます...実際、私はこれを一緒に投げました:)

編集:これを書くことを通して、私は彼らがそれを実装しなかった理由を発見しました。スコープがローカルであるため、匿名タイプを処理できません。したがって、コールバック関数を定義する方法はありません。 多くのlinqtosqlのものがselect句でそれらを作成するので、これはかなり重要なことです。以下の提案はどれも同じ運命をたどるので、私はまだこれが最も使いやすいと思います!

編集:唯一の解決策は、匿名タイプを使用しないことです。コールバックをIEnumerable(タイプargsなし)を取得するものとして宣言し、リフレクションを使用してフィールドにアクセスできます(ICK !!)。別の方法は、コールバックを「動的」として宣言することです...ああ...待ってください...それはまだ出ていません。:)これは、ダイナミックを使用する方法のもう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;
        }
    }

}

行くつもりです、これを今私のブログに載せてください、かなり便利です。

于 2008-10-31T02:05:54.047 に答える
16

TheSoftwareJedi とulrikbの (別名 user316318) のソリューションは、あらゆる LINQ タイプに適していますが、( Chris Moschiniが指摘したように) Windows I/O Completion Ports を利用する基礎となる非同期呼び出しに委譲しません。

Wesley Bakker のAsynchronous DataContextの投稿 ( Scott Hanselman のブログ投稿がきっかけ) では、Windows I/O Completion Ports を利用する sqlCommand.BeginExecuteReader/sqlCommand.EndExecuteReader を使用する LINQ to SQL のクラスについて説明しています。

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

于 2011-07-03T08:21:16.227 に答える
4

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

  1. LINQ-to-SQLに、を介して変換するという「重い」作業を実行さIQueryableDbCommandますDataContext.GetCommand()
  2. SQL 200 [058]の場合、取得した抽象DbCommandインスタンスからキャストして、GetCommand()を取得しSqlCommandます。SQL CEを使用している場合は、との非同期パターンが公開されていないため、運が悪いSqlCeCommandです。BeginExecuteReaderEndExecuteReader
  3. 標準の.NETFramework非同期I/Oパターンを使用BeginExecuteReaderEndExecuteReaderたり、使用したりして、メソッドに渡す完了コールバックデリゲートを取得します。SqlCommandDbDataReaderBeginExecuteReader
  4. これで、DbDataReaderどの列が含まれているのか、それらの値をIQueryable'sにマップする方法ElementType(結合の場合は匿名型である可能性が高い)がわからないことがわかりました。確かに、この時点で、結果を匿名型などに具体化する独自の列マッパーを手書きすることができます。LINQ-to-SQLがIQueryableをどのように処理し、どのSQLコードを生成するかに応じて、クエリ結果タイプごとに新しいものを作成する必要があります。これはかなり厄介なオプションであり、保守が不可能であり、常に正しいとは限らないため、お勧めしません。LINQ-to-SQLは、渡したパラメータ値に応じてクエリフォームを変更できます。たとえばquery.Take(10).Skip(0)、SQLとは異なるSQLを生成します。query.Take(10).Skip(10)、そしておそらく別の結果セットスキーマ。あなたの最善の策は、この具体化の問題をプログラムで処理することです。
  5. のTypeDbDataReaderのLINQからSQLへのマッピング属性に従って、定義された順序で列をプルオフする単純なランタイムオブジェクトマテリアライザーを「再実装」します。これを正しく実装することは、おそらくこのソリューションの最も難しい部分です。ElementTypeIQueryable

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

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

  1. IQueryableの最後のselect句で、単一のオブジェクトインスタンスを複数の匿名型プロパティにマップすることはできませんfrom x in db.Table1 select new { a = x, b = x }。LINQ-to-SQLは、どの列序数がどのプロパティにマップされるかを内部的に追跡します。この情報はエンドユーザーに公開されないため、のどの列DbDataReaderが再利用され、どの列が「個別」であるかがわかりません。
  2. 最終的なselect句に定数値を含めることはできません-これらはSQLに変換されず、存在しないため、これらの定数値をツリーDbDataReaderからプルアップするカスタムロジックを構築する必要があります。面倒で、単に正当化されません。IQueryableExpression

壊れるかもしれない他のクエリパターンがあると確信していますが、これらは、既存のLINQ-to-SQLデータアクセス層で問題を引き起こす可能性があると私が考えることができる2つの最大のものです。

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

問題を解決するためのより「完全な」アプローチは、LINQ-to-SQLのほぼすべてを効果的に再実装することです。これは、もう少し時間がかかります:-P。質の高いものから始めて、オープンソースのLINQ-to-SQLプロバイダーの実装がここに行く良い方法です。再実装が必要な理由は、DbDataReader情報を失うことなく、結果をオブジェクトインスタンスにバックアップするために使用されるすべての列マッピング情報にアクセスできるようにするためです。

于 2011-08-16T21:15:07.947 に答える