使用Async和Await分解数据库调用(使用Dapper)

And*_*rke 10 c# asynchronous async-await dapper

我们从Dapper请求数千个对象并达到参数限制(2100),因此决定以块的形式加载它们.

我认为这将是一个尝试异步等待的好机会 - 这是我第一次走了,所以也许会让学校男生出错!

断点正在受到打击,但整个事情都没有回归.这不是一个错误 - 它似乎只是一切都在黑洞!

请帮忙!

这是我原来的方法 - 它现在调用Async方法

    public List<MyObject> Get(IEnumerable<int> ids)
    {
        return this.GetMyObjectsAsync(ids).Result.ToList();
    }  //Breakpoint on this final bracket never gets hit
Run Code Online (Sandbox Code Playgroud)

我添加了此方法将id拆分为1000块,然后等待任务完成

    private async Task<List<MyObject>> GetMyObjectsAsync(IEnumerable<int> ids)
    {
        var subSets = this.Partition(ids, 1000);

        var tasks = subSets.Select(set => GetMyObjectsTask(set.ToArray()));

        //breakpoint on the line below gets hit ...
        var multiLists = await Task.WhenAll(tasks);

        //breakpoint on line below never gets hit ...
        var list = new List<MyObject>();
        foreach (var myobj in multiLists)
        {
            list.AddRange(myobj);   
        }
        return list;
    }
Run Code Online (Sandbox Code Playgroud)

以下是任务......

    private async Task<IEnumerable<MyObject>> GetMyObjectsTask(params int[] ids)
    {
        using (var db = new SqlConnection(this.connectionString))
        {
            //breakpoint on the line below gets hit
            await db.OpenAsync();
            return await db.QueryAsync<MyObject>(@"SELECT Something FROM Somewhere WHERE ID IN @Ids",
            new { ids});
        }
    }
Run Code Online (Sandbox Code Playgroud)

以下方法只是将块中的id列表拆分 - 这似乎工作正常...

    private IEnumerable<IEnumerable<T>> Partition<T>(IEnumerable<T> source, int size)
    {
        var partition = new List<T>(size);
        var counter = 0;

        using (var enumerator = source.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                partition.Add(enumerator.Current);
                counter++;
                if (counter % size == 0)
                {
                    yield return partition.ToList();
                    partition.Clear();
                    counter = 0;
                }
            }

            if (counter != 0)
                yield return partition;
        }
    }
Run Code Online (Sandbox Code Playgroud)

Dir*_*irk 12

您正在以与您async/await结合使用的方式创建死锁情况Task<T>.Result.

违规行是:

return this.GetMyObjectsAsync(ids).Result.ToList();
Run Code Online (Sandbox Code Playgroud)

GetMyObjectsAsync(ids).Result阻止当前的同步上下文.但用于为当前同步上下文调度延续点的GetMyObjectsAsync用途await.我确信你可以看到这种方法的问题:永远不能执行延续,因为当前的同步上下文被阻止了Task.Result.

在某些情况下可以使用的一种解决方案是使用ConfigureAwait(false),这意味着可以在任何同步上下文上运行延续.

但总的来说,我认为最好避免Task.Result使用async/await.


请注意,实际发生此死锁情况取决于调用异步方法时使用的同步上下文.对于ASP.net,Windows Forms和WPF,这将导致死锁,但据我所知,它不适用于控制台应用程序.(感谢Marc Gravell的评论)


Microsoft有一篇关于异步编程最佳实践的好文章.(感谢ken2k)

  • 好吧,这让我有点打字,谢谢 (3认同)
  • @Dirk严格来说,是否导致死锁取决于正在使用的“同步上下文”。有时它会按书面形式正常工作-有时(asp.net,winforms,wpf等):不会(正如您所说的那样:死锁)。`ConfigureAwait`是一个选项(实际上避免了同步上下文,而不是专门针对线程),但是我同意:将`await`和`.Result`混合使用是一种痛苦的秘诀。 (2认同)