Asyn c задач на разных dbContexts. Говорит, что в этом контексте началась вторая операция - PullRequest
1 голос
/ 01 апреля 2020
public static async void DoSomething(IEnumerable<IDbContext> dbContexts)
{
    IEnumerator<IDbContext> dbContextEnumerator = dbContexts.GetEnumerator();

    Task<ProjectSchema> projectSchemaTask = Task.Run(() => Core.Data.ProjectRead
        .GetAll(dbContextEnumerator.Current)
        .Where(a => a.PJrecid == pjRecId)
        .Select(b => new ProjectSchema
        {
            PJtextid = b.PJtextid,
            PJcustomerid = b.PJcustomerid,
            PJininvoiceable = b.PJininvoiceable,
            PJselfmanning = b.PJselfmanning,
            PJcategory = b.PJcategory
        })
        .FirstOrDefault());

    Task<int?> defaultActivitySchemeTask = projectSchemaTask.ContinueWith(antecedent =>
    {
        //This is where an exception may get thrown
        return ProjectTypeRead.GetAll(dbContextEnumerator.Current)
            .Where(a => a.PTid == antecedent.Result.PJcategory)
            .Select(a => a.PTactivitySchemeID)
            .FirstOrDefaultAsync().Result;
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    Task<SomeModel> customerTask = projectSchemaTask.ContinueWith((antecedent) =>
    {
        //This is where an exception may get thrown
        return GetCustomerDataAsync(antecedent.Result.PJcustomerid,
            dbContextEnumerator.Current).Result;
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    await Task.WhenAll(defaultActivitySchemeTask, customerTask);
}

Исключение, которое я получаю:

NotSupportedException: вторая операция началась в этом контексте до завершения предыдущей асинхронной операции. Используйте 'await', чтобы убедиться, что все асинхронные операции завершены, прежде чем вызывать другой метод в этом контексте. Ни один из членов экземпляра не гарантированно является потокобезопасным.

Исключение выдается только после каждых 1/20 вызовов этой функции. И, похоже, исключение возникает только тогда, когда я объединяю задачи с ContinueWith().

Как может быть вторая операция в контексте, когда я использую новую для каждого запроса?

Это всего лишь пример моего кода. В реальном коде у меня есть 3 родительские задачи, и к каждому из них прикреплено 1-5 связанных задач.

Что я делаю не так?

Ответы [ 2 ]

4 голосов
/ 01 апреля 2020

да, вы в основном не должны использовать ContinueWith в эти дни; в этом случае вы получаете два продолжения одной и той же задачи (для defaultActivitySchemeTask и customerTask); как они взаимодействуют, теперь в основном не определено, и будет зависеть от того, как именно работают два асинхронных c -потока, но вы можете абсолютно закончить с перекрывающимися асинхронными c операциями здесь (например, в самом простом «продолжения последовательны», как только первое ожидает, потому что оно неполное, второе начнется). Честно говоря, это должен быть логически последовательный код, основанный на await, вероятно, не использующий также Task.Run, но давайте сохраним его сейчас:

ProjectSchema projectSchema = await Task.Run(() => ...);

int? defaultActivityScheme = await ... first bit
SomeModel customer = await ... second bit

Мы не можем выполнять два подчиненных запроса одновременно, не рискуя одновременно asyn c операции в одном контексте.

0 голосов
/ 01 апреля 2020

В вашем примере вы, кажется, запускаете два продолжения параллельно, поэтому существует вероятность, что они будут перекрываться, вызывая проблему параллелизма. DbContext не является потокобезопасным, поэтому вам необходимо убедиться, что ваши асинхронные вызовы являются последовательными. Имейте в виду, что использование async / await просто превратит ваш код в конечный автомат, чтобы вы могли контролировать, какие операции завершены, прежде чем переходить к следующей операции. Использование одних только асинхронных c методов не обеспечит параллельные операции, а завершение вашей операции в Task.Run. Поэтому вам нужно спросить себя, действительно ли Task.Run действительно требуется (т. Е. Планирование работы в ThreadPool), чтобы сделать его параллельным.

Вы упомянули, что в вашем реальном коде у вас есть 3 родительские задачи, и у каждого из них есть 1-5 связанных задач прилагается к ним. Если у 3 родительских задач есть отдельные DbContexts, они могут выполняться параллельно (каждая из них заключена в Task.Run), но их цепочечные продолжения должны быть последовательными (используя ключевые слова async / await). Например:

        public async Task DoWork()
        {
            var parentTask1 = Task.Run(ParentTask1);
            var parentTask2 = Task.Run(ParentTask2);
            var parentTask3 = Task.Run(ParentTask3);

            await Task.WhenAll(parentTask1 , parentTask2, parentTask3);
        }

        private async Task ParentTask1()
        {
            // chained child asynchronous continuations
            await Task.Delay(100);
            await Task.Delay(100);
        }

        private async Task ParentTask2()
        {
            // chained child asynchronous continuations
            await Task.Delay(100);
            await Task.Delay(100);
        }

        private async Task ParentTask3()
        {
            // chained child asynchronous continuations
            await Task.Delay(100);
            await Task.Delay(100);
        }

Если ваши родительские задачи работают с одним и тем же DbContext, во избежание параллелизма вам нужно будет ожидать их один за другим (не нужно заключать их в Task.Run):

        public async Task DoWork()
        {
            await ParentTask1();
            await ParentTask2();
            await ParentTask3();
        }

...