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

本文关键字:使用 调用 Dapper 数据库 分解 Async Await | 更新日期: 2023-09-27 18:11:31

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

我认为这将是一个很好的机会,尝试Async等待-这是我第一次去,所以也许使一个学校男孩的错误!

断点被击中了,但是整个东西没有返回。它并没有抛出一个错误——它看起来就像所有的东西都在黑洞里运行!

请帮助!

这是我最初的方法-它现在调用Async方法

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

我添加了这个方法来将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;
    }

及以下是任务…

    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});
        }
    }

下面的方法只是将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;
        }
    }

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

如果您将async/awaitTask<T>.Result结合使用,则会导致死锁。

错误行是:

return this.GetMyObjectsAsync(ids).Result.ToList();

GetMyObjectsAsync(ids).Result阻塞当前同步上下文。但是GetMyObjectsAsync使用await,它为当前同步上下文调度一个延续点。我相信你可以看到这种方法的问题:由于当前的同步上下文被Task.Result阻塞,因此永远无法执行延续。

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

但总的来说,我认为最好避免Task.Resultasync/await


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


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

我认为参数是区分大小写的它应该是:

return await db.QueryAsync<MyObject>(@"SELECT Something FROM Somewhere WHERE ID IN @ids",
            new { ids});
 return await db.QueryAsync<MyObject>(@"SELECT Something FROM Somewhere WHERE ID IN **@Ids**",
            new { ids});

不确定,但不妨试试