Использование Async и Await для разрыва вызова базы данных (с Dapper)
Мы запрашиваем тысячи объектов у Dapper и нажимаем на ограничение параметра (2100), поэтому решили загрузить их в куски.
Я подумал, что это была бы хорошая возможность попробовать Async Await - это первый раз, когда я отправился, поэтому, возможно, ошибся школьный мальчик!
Точки останова попадают, но все это просто не возвращается. Это не бросает ошибку - кажется, что все идет в черной дыре!
Помогите пожалуйста!
Это был мой оригинальный метод - теперь он вызывает метод Async
public List<MyObject> Get(IEnumerable<int> ids)
{
return this.GetMyObjectsAsync(ids).Result.ToList();
} //Breakpoint on this final bracket never gets hit
Я добавил этот метод, чтобы разделить идентификаторы на куски 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});
}
}
Следующий метод просто разбивает список идентификаторов в кусках - это выглядит нормально...
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;
}
}
Ответы
Ответ 1
Вы создаете тупиковую ситуацию, когда используете async/await
в комбинации с Task<T>.Result
.
Строка нарушения:
return this.GetMyObjectsAsync(ids).Result.ToList();
GetMyObjectsAsync(ids).Result
блокирует текущий контекст синхронизации. Но GetMyObjectsAsync
использует await
, который планирует точку продолжения для текущего контекста синхронизации. Я уверен, что вы можете увидеть проблему с этим подходом: продолжение никогда не может быть выполнено, потому что текущий контекст синхронизации заблокирован Task.Result
.
Одним из решений, которое может работать в некоторых случаях, было бы использование ConfigureAwait(false)
, что означает, что продолжение может выполняться в любом контексте синхронизации.
Но в целом я считаю, что лучше избегать Task.Result
с async/await
.
Обратите внимание, что на самом деле происходит ли эта ситуация взаимоблокировки, зависит от контекста синхронизации, который используется при вызове асинхронного метода. Для ASP.net, Windows Forms и WPF это приведет к взаимоблокировке, но насколько я знаю, это не будет для консольного приложения. (Спасибо Марку Гравелю за его комментарий)
В Microsoft есть хорошая статья о "Лучшие практики в асинхронном программировании" . (Благодаря ken2k)
Ответ 2
Я думаю, что параметры чувствительны к регистру:
return await db.QueryAsync<MyObject>(@"SELECT Something FROM Somewhere WHERE ID IN @ids",
new { ids});
вместо "@Ids" ниже в запросе:
return await db.QueryAsync<MyObject>(@"SELECT Something FROM Somewhere WHERE ID IN **@Ids**",
new { ids});
не уверен, но просто попробую.