簡體   English   中英

如何編寫異步LINQ查詢?

[英]How to write Asynchronous LINQ query?

閱讀了一堆與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(無類型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;
        }
    }

}

現在將其放在我的博客上,非常方便。

TheSoftwareJedi和ulrikb (aka user316318)解決方案適用於任何LINQ類型,但是(如Chris Moschini所指出的)不委托給利用Windows I / O完成端口的底層異步調用。

Wesley Bakker的Asynchronous DataContext帖子(由Scott Hanselman的博客帖子觸發)描述了LINQ to SQL的類,該類使用sqlCommand.BeginExecuteReader / sqlCommand.EndExecuteReader,該類利用Windows I / 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查詢執行。 這個想法很簡單,盡管在這個階段(截至2011年8月16日)“脆弱”:

  1. 讓LINQ-to-SQL進行“繁重”的工作,即通過DataContext.GetCommand()IQueryable轉換為DbCommand
  2. 對於SQL 200 [058],從您從GetCommand()獲得的抽象DbCommand實例進行GetCommand()以獲取SqlCommand 如果您使用的是SQL CE,那么您會很不走運,因為SqlCeCommand不會公開BeginExecuteReaderEndExecuteReader的異步模式。
  3. 使用標准.NET Framework異步I / O模式在SqlCommand使用BeginExecuteReaderEndExecuteReader ,可以在傳遞給BeginExecuteReader方法的完成回調委托中獲得DbDataReader
  4. 現在,我們有了一個DbDataReader ,它不知道它包含哪些列,也不知道如何將這些值映射回IQueryableElementType (對於聯接,很可能是匿名類型)。 當然,這時您可以手寫自己的列映射器,將其結果具體化為您的匿名類型或其他類型。 您必須為每種查詢結果類型編寫一個新的查詢類型,具體取決於LINQ-to-SQL如何對待您的IQueryable及其生成的SQL代碼。 這是一個很討厭的選擇,我不建議這樣做,因為它無法維護,也不總是正確的。 LINQ-to-SQL可以根據您傳入的參數值更改查詢形式,例如query.Take(10).Skip(0)生成與query.Take(10).Skip(10)不同的SQL,也許不同的結果集架構。 最好的選擇是以編程方式解決此實現問題:
  5. “重新實現”一個簡單化的運行時對象實現器,該實現器根據IQueryableElementType類型的LINQ到SQL映射屬性按定義的順序從DbDataReader中拉出列。 正確實施此方法可能是此解決方案中最具挑戰性的部分。

正如其他人發現的那樣, DataContext.Translate()方法不能處理匿名類型,只能將DbDataReader直接映射到屬性正確的LINQ-to-SQL代理對象。 由於大多數值得在LINQ中編寫的查詢都將涉及復雜的聯接,這些聯接不可避免地最終需要為最終的select子句使用匿名類型,因此無論如何使用此提供的淡化DataContext.Translate()方法是毫無意義的。

利用現有的成熟LINQ-to-SQL IQueryable提供程序時,此解決方案有一些小缺點:

  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數據訪問層中引起問題。

這些問題很容易克服-根本不要在查詢中執行這些問題,因為這兩種模式都不會對查詢的最終結果產生任何好處。 希望此建議適用於所有可能導致對象實現問題的查詢模式:-P。 解決無法訪問LINQ-to-SQL的列映射信息是一個難題。

解決問題的更“完整”的方法是有效地重新實現幾乎所有的LINQ-to-SQL,這是比較耗時的:-P。 從高質量的,開源的LINQ-to-SQL提供程序實現開始,將是一個不錯的選擇。 您需要重新實現它的原因是,這樣您就可以訪問所有用於實現DbDataReader結果的列映射信息,並將其備份回對象實例,而不會丟失任何信息。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM