2013-10-27 229 views
7

使用Azure存储客户端库2.1,我正在查询表存储异步。我创造了这个代码:如何使用Table.ExecuteQuerySegmentedAsync()与Azure表存储

public async Task<List<TAzureTableEntity>> GetByPartitionKey(string partitionKey) 
{ 
    var theQuery = _table.CreateQuery<TAzureTableEntity>() 
         .Where(tEnt => tEnt.PartitionKey == partitionKey); 
    TableQuerySegment<TAzureTableEntity> querySegment = null; 
    var returnList = new List<TAzureTableEntity>(); 
    while(querySegment == null || querySegment.ContinuationToken != null) 
    { 
     querySegment = await theQuery.AsTableQuery() 
            .ExecuteSegmentedAsync(querySegment != null ? 
             querySegment.ContinuationToken : null); 
     returnList.AddRange(querySegment); 
    } 
    return returnList; 
} 

让我们假设有一个大的数据集回来所以会有很多往返表存储的。我遇到的问题是我们正在等待一组数据,将它添加到内存列表中,等待更多数据,将它添加到同一个列表中,等待更多数据,将它添加到列表中...等等等等。为什么不只是围绕常规TableQuery包装Task.Factory.StartNew()?像这样:

public async Task<List<TAzureTableEntity>> GetByPartitionKey(string partitionKey) 
{ 
    var returnList = await Task.Factory.StartNew(() => 
               table.CreateQuery<TAzureTableEntity>() 
               .Where(ent => ent.PartitionKey == partitionKey) 
               .ToList()); 
    return returnList; 
} 

这样做,这样好像我们没有弹跳的SynchronizationContext来回这么多。还是它真的很重要?

编辑来修改你的问题

什么是上面提到的两种情况之间的差异?

+1

你把你的“片段”在它自己的异步方法和使用调用它ConfigureAwait(假)。 –

+0

@PauloMorgado - 具有讽刺意味的是,实际上我已经在单独的方法中使用了TableQuerySegement和while语句,但是我不知道Task上的ConfigureAwait(false)方法。谢谢你的提示! – Hallmanac

+2

对于所有库代码,建议使用ConfigureAwait(false),并将其扩展到所有非UI应用程序代码。 –

回答

6

两者之间的区别在于,您的第二个版本将在整个查询执行期间阻塞ThreadPool线程。在GUI应用程序中(这里您只需要执行UI线程以外的其他代码),这可能是可以接受的,但是它会在服务器应用程序中取消async的任何可伸缩性优势。

另外,如果你不想让你的第一个版本,返回到每个往返的UI方面(这是一个合理的要求),然后使用ConfigureAwait(false)只要您使用await

querySegment = await theQuery.AsTableQuery() 
          .ExecuteSegmentedAsync(…) 
          .ConfigureAwait(false); 

这样,在第一个之后的所有迭代将(很可能)在ThreadPool线程上执行,而不是在UI上下文中执行。

顺便说一句,在你的第二个版本,你实际上并不需要await可言,你可以只直接返回Task

public Task<List<TAzureTableEntity>> GetByPartitionKey(string partitionKey) 
{ 
    return Task.Run(() => table.CreateQuery<TAzureTableEntity>() 
           .Where(ent => ent.PartitionKey == partitionKey) 
           .ToList()); 
} 
+0

querySegment(第一版本)如何协助(而不是否定)异步的可伸缩性优势?所有的东西都是平等的,你仍然占用一个'ThreadPool'线程。特别是在使用'ConfigureAwait(false)'时。我的想法是否正确? – Hallmanac

+1

@Hallmanac不,那是错误的。 “await”的意思是,当异步操作执行时,你不占用任何线程。使用'ConfigureAwait(false)',您只在段之间的短时间内使用'ThreadPool'线程来启动下一个段。 – svick

+2

Gotcha。没有意识到它在等待QuerySegment返回数据时没有占用ThreadPool线程。具有讽刺意味的是,在深入探讨你以前的评论时,我发现你在这个问题上给出了另一个好的答案。 :-)如果其他人发现它和我一样有用,这里是链接。 http://stackoverflow.com/a/14898584/350312 – Hallmanac

2

不知道这是你正在寻找的答案,但我仍然想提到它:)。

正如您可能已经知道的那样,第二种方法(使用Task)在内部处理继续标记,并在所有实体都被提取时从方法中提取出来,而第一种方法提取一组实体(最大值为1000)然后出来给你的结果集,以及一个延续令牌。

如果您有兴趣从表中获取所有实体,则可以使用这两种方法,但第一种方法可以随时随地优雅地跳出循环,而第二种方法无法获得灵活性。所以使用第一个功能你可以基本上引入分页概念。

假设您正在构建一个显示表中数据的Web应用程序。进一步让我们假设表中包含大量的实体(比方说100000个实体)。使用第一种方法,您可以获取1000个实体将结果返回给用户,如果用户需要,您可以获取下一组1000个实体并将其显示给用户。您可以继续这样做,直到用户需要的时间和表格中的数据。用第二种方法,用户必须等待从表中取出所有100000个实体。

+0

这是我考虑这个的另一个原因。如果我要欺骗/强制该方法返回真正的IEnumerable,那么我会否定异步/等待的好处。尽管你的分页用例让我想起了一点。您必须将继续令牌传递给使用者,以便他们可以将其传递回存储库,以便从中断处继续。在这种情况下,这将是一个很好的异步用例。 – Hallmanac