C# 使用Async和Wait中断数据库调用(使用Dapper)

C# 使用Async和Wait中断数据库调用(使用Dapper),c#,asynchronous,async-await,dapper,C#,Asynchronous,Async Await,Dapper,我们请求Dapper返回数千个对象,并达到参数限制(2100),因此决定将它们分块加载 我想这将是一个尝试Async Wait的好机会-这是我第一次尝试,所以可能会犯一个学童错误 断点正在命中,但整个过程都没有返回。它并没有抛出一个错误——它只是看起来一切都在一个黑洞中进行 救命啊 这是我最初的方法-它现在调用异步方法 public List<MyObject> Get(IEnumerable<int> ids) { return this

我们请求Dapper返回数千个对象,并达到参数限制(2100),因此决定将它们分块加载

我想这将是一个尝试Async Wait的好机会-这是我第一次尝试,所以可能会犯一个学童错误

断点正在命中,但整个过程都没有返回。它并没有抛出一个错误——它只是看起来一切都在一个黑洞中进行

救命啊

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

    public List<MyObject> Get(IEnumerable<int> ids)
    {
        return this.GetMyObjectsAsync(ids).Result.ToList();
    }  //Breakpoint on this final bracket never gets hit
公共列表获取(IEnumerable ID)
{
返回此.GetMyObjectsAsync(ids.Result.ToList();
}//最后一个括号上的断点永远不会被命中
我添加了这个方法,将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;
    }
专用异步任务GetMyObjectsAsync(IEnumerable ID)
{
var子集=这个分区(id,1000);
var tasks=subSets.Select(set=>GetMyObjectsTask(set.ToArray());
//下面一行上的断点被击中。。。
var multiLists=wait Task.WhenAll(任务);
//下面一行上的断点永远不会被击中。。。
var list=新列表();
foreach(多重列表中的var myobj)
{
list.AddRange(myobj);
}
退货清单;
}
下面是任务

    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});
        }
    }
专用异步任务GetMyObjectStatask(参数int[]ids)
{
使用(var db=new-SqlConnection(this.connectionString))
{
//下面一行上的断点被命中
等待db.OpenAsync();
return wait db.QueryAsync(@“从@Ids中ID所在的地方选择某物”,
新的{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;
        }
    }
私有IEnumerable分区(IEnumerable源,int-size)
{
var分区=新列表(大小);
var计数器=0;
使用(var enumerator=source.GetEnumerator())
{
while(枚举数.MoveNext())
{
添加(枚举数.Current);
计数器++;
如果(计数器%size==0)
{
产生返回分区。ToList();
partition.Clear();
计数器=0;
}
}
如果(计数器!=0)
收益率分割;
}
}

您将使用
async/await
Task.Result
相结合的方式创建死锁情况

令人不快的是:

return this.GetMyObjectsAsync(ids).Result.ToList();
GetMyObjectsAsync(ids)。结果
阻止当前同步上下文。但是
GetMyObjectsAsync
使用
await
为当前同步上下文安排一个延续点。我相信您可以看到这种方法的问题:由于当前同步上下文被
Task.Result
阻止,因此无法执行继续

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

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


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



微软有一篇关于这方面的好文章。(感谢ken2k)

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

return await db.QueryAsync<MyObject>(@"SELECT Something FROM Somewhere WHERE ID IN @ids",
            new { ids});
return wait db.QueryAsync(@“从@ids中ID所在的地方选择某物”,
新的{ids});
而不是查询中下面的“@Ids”:

 return await db.QueryAsync<MyObject>(@"SELECT Something FROM Somewhere WHERE ID IN **@Ids**",
            new { ids});
return await db.QueryAsync(@“从**@Ids**中ID所在的位置选择某物”,
新的{ids});

虽然不确定,但请尝试。

您可以通过GetMyObjectsAsync使用调试器吗?它能跑完吗?+1。另外,我觉得有义务分享这篇文章:@Dirk严格来说,这是否会导致死锁取决于使用的“同步上下文”;有时它会像编写的那样工作良好——有时(asp.net、winforms、wpf等):它不会(正如您所说:死锁)
ConfigureAwait
是一种选择(它实际上避免了同步上下文,而不是专门针对线程),但我同意:混合
await
。Result
是pain@MarcGravell谢谢我已经更新了我的答案,加入了你的评论。更好的方法是什么?@ChristianSauer要么一直使用异步,例如直到事件处理程序。或者,如果无法执行,请使用Task.ContinueWith而不是Task.Result。