Использование 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});

не уверен, но просто попробую.