какой уровень должен заполнять шаблон параллелизма и задачи в N-уровневой архитектуре в .NET

#c# #.net #async-await #task #n-tier-architecture

#c# #.net #async-await #задача #n-уровневая архитектура

Вопрос:

Пожалуйста, если этот вопрос не по теме, вы можете написать в комментарии, чтобы удалить его позже 🙂

Если у меня есть 3 уровня (* .DLL)

  • DataAccessLayer.DLL
  • BusinessLogicLayer.DLL
  • Приложение пользовательского интерфейса (WPF, WinForms, любая технология)

В моем приложении я заставляю методы BusinessLogicLayer возвращать задачу, а в приложении пользовательского интерфейса я вызываю await при использовании метода из BusinessLogicLayer

У меня неправильное понимание следующего:

  • Какой уровень должен поддерживаться с помощью задачи или параллелизма (серверный, промежуточный, передний)
  • Если мне нужно создать повторно используемый BusinessLogicLayer для других разработчиков, где они также могут забыть использовать ожидаемые / асинхронные методы на прикладном уровне пользовательского интерфейса в следующих проектах, которые были обработаны для них. Как добиться ожидаемых методов на среднем уровне were без необходимости писать await в каждом событии пользовательского интерфейса (например, Button_Click ).
  • Как сделать так, чтобы DataAccessLayer содержал только параллелизм и ожидаемый уровень пользовательского интерфейса без необходимости использования ключевых слов await, async.

Мой простой код содержит следующее :

DataAccessLayer.DLL единственный метод

 public void MoveNext()
{
    if (RecordCount == 0) return;
    Tables[0].Query.Criteria.Clear();
    Tables[0].Query.Sorting = $"ORDER BY {string.Join(" ASC, ", 
    Tables[0].Keys.Select(x => x.KeyID))} ASC";
    FetchDataBuffer();
}
  

BusinessLogicLayer.DLL теперь простым способом обернула метод dataaccess

 public Task MoveNext() => Task.Run(() => { EntryBase.MoveNext(); });
  

Уровень пользовательского интерфейса (любое приложение или поставщик интерфейсов в .NET)

  private async void BtnNext_ItemClick(object sender, ClickEventArgs e)
 {
    await EntryLogic.MoveNext();
    DeserializeBuffer();
 }
  

Как видно выше, метод DeserializeBuffer не будет выполняться до тех пор, пока не будет завершен метод параллелизма (MoveNext ).

Что мне нужно сделать, так это избавиться от ключевых слов await и асинхронности на уровне пользовательского интерфейса.

То, что я на самом деле делаю, не удалось, и я не знаю, почему это происходит

Сценарий, который я пытаюсь создать, выглядит следующим образом:

  • Преобразуйте тип метода DataAccessLayer из void в Task

        public Task MoveNext()
       {
          return Task.Run(() => { 
          if (RecordCount == 0) return;
          Tables[0].Query.Criteria.Clear();
          Tables[0].Query.Sorting = $"ORDER BY {string.Join(" ASC, ", 
          Tables[0].Keys.Select(x => x.KeyID))} ASC";
          FetchDataBuffer();
          });
         }
      
  • Затем вызовите await на промежуточном уровне BusinessLogicLayer

    общедоступная асинхронная пустота MoveNext() => await EntryBase.MoveNext();

  • После этого вызывается MoveNext в UI Layer из Logic layer. я предполагаю, что это сделает его доступным для ожидания, потому что он уже объявил await на промежуточном уровне. но на самом деле уровень пользовательского интерфейса выполняет следующий метод одновременно. итак, исключения, вызванные отключением следующего метода (DeserializeBuffer) в зависимости от предыдущего метода (EntryLogic.MoveNext)

       private async void BtnNext_ItemClick(object sender, ClickEventArgs e)
      {
        EntryLogic.MoveNext();
        DeserializeBuffer();  // exception thrown   
         /* because EntryLogic.MoveNext 
         is still executing and not awaited */
      }
      

Будем признательны за любую помощь.

Комментарии:

1. «избавьтесь от ключевых слов await и асинхронности на уровне пользовательского интерфейса». — это противоположно направлению, в котором работало бы большинство людей. Почему вы считаете, что это правда?

2. если вы используете async / task на любом уровне, где-то это должно ожидаться или вам нужно вызвать .Result . Уровень пользовательского интерфейса хорош для ожидания, вы должны сделать это в любом случае, если вы не хотите, чтобы поток пользовательского интерфейса зависал до тех пор, пока задача не будет выполнена

3. Везде . Tasks должен отображаться на всех уровнях, если базовая работа по своей сути асинхронна.

4. Я искренне надеюсь, что у вас это не загромождает вашу кодовую базу: public Task MoveNext() => Task.Run(() => { EntryBase.MoveNext(); }); в противном случае просто забудьте об async / await или реорганизуйте свою кодовую базу для использования собственных методов на основе задач. Использование задачи. Запуск для переноса вызовов синхронных методов — плохая практика.

5. Создайте новое многоуровневое небольшое решение без какой-либо задачи, асинхронности и ожидания. Если это работает, то повторите его с помощью task, async и await до конца. Вы бы легко поняли, как это должно быть сделано.

Ответ №1:

Что мне нужно сделать, так это избавиться от ключевых слов await и async в слое пользовательского интерфейса.

Это совершенно неверно. Уровень пользовательского интерфейса — это единственное место, где async и await должен использоваться. И они должны использоваться на этом уровне.

Ваша технология доступа к данным не указана, но из кода я предполагаю, что она может быть на DataTable основе, что проблематично, поскольку DataTable она чрезвычайно старая и не поддерживается async . Обратите внимание, что обертывание тел методов с помощью Task.Run , чтобы «сделать их асинхронными», является антишаблоном — на самом деле это поддельно-асинхронные методы, а не по-настоящему асинхронные.

Если я ошибаюсь и ваша технология доступа к данным поддерживает async , то вы должны иметь возможность создавать свои методы DAL async без использования Task.Run . Начните с самого низкого уровня (например, любых FetchDataBuffer вызовов методов) и измените их на асинхронные эквиваленты. Тогда давайте async развиваться оттуда. Обратите внимание, что «разрешить асинхронный рост» означает использовать async Task , а не async void ; async void в BLL, безусловно, антипаттерн.

Но если я прав и ваш DAL использует DataTable , то вам нужно решить, следует ли переключаться на более новую технологию доступа к данным. Если это не то, что вы можете сделать прямо сейчас, то я бы рекомендовал сохранить существующий код DAL и BLL и просто добавить async / await на уровень пользовательского интерфейса:

 private async void BtnNext_ItemClick(object sender, ClickEventArgs e)
{
  await Task.Run(() => EntryLogic.MoveNext());
  DeserializeBuffer();
}
  

Это не антишаблон, потому что мы используем его Task.Run для вызова метода — для перемещения его из потока пользовательского интерфейса. Это не идеально, поскольку мы по-прежнему используем больше потоков, чем необходимо, но идеальное решение потребовало бы настоящего асинхронного доступа к данным. При таком компромиссе ваши DAL и BLL по-прежнему блокируются, поэтому их ограниченное использование за пределами приложений пользовательского интерфейса для настольных компьютеров.

Комментарии:

1. Сэр, вы действительно поняли это. ответил на это хорошо. действительно спасибо за вашу соответствующую помощь, о технологии доступа к данным, да, я использую DataTable из-за заполнения GridViews, приложение, с которым я работаю, полно динамического, все должно быть во время выполнения (создание таблиц, добавление новых полей), поэтому я думаю, Entity Framework не будет работать со мной. не могли бы вы, пожалуйста, предоставить более новую технологию доступа к данным, которая легко привязывается к сеткам (например, коллекции, списки и т.д., idk ), если вы не возражаете, предоставьте несколько ссылок на вашу хорошо написанную задачу и асинхронные вещи в вашем блоге к предоставленному вами ответу 🙂

2. В новых технологиях доступа к данным нет ничего подобного Fill ; в конечном итоге вам приходится создавать свои собственные ObservableCollection<T> шаблоны и еще много чего. Entity Framework — прекрасный подход, как и Dapper. Ни один из новых не является таким простым, как таблицы данных, но ни один из них также не является настолько неэффективным.

3. вы имеете в виду, что .NET стал бесполезным 🙂 SqlDataReader имеет асинхронные методы, поэтому доступны только старые datatable, наборы данных старые, пожалуйста, можете ли вы сказать, что такое технология доступа к данным, которая хороша для динамического создания, добавления новых таблиц, полей во время выполнения. я думаю, ObservableCollection<T> сложно разработать! Простой вариант хорош!

4. после прочтения некоторых ваших статей обнаружил, что Task.Run() — плохая практика для ASP.NET приложения. чтобы реализовать асинхронность, задачу только в пользовательском интерфейсе? не в BLL это правда о том, что я получил?

5. @abtka: Да; ваш лучший выбор — либо обновить DAL до современной технологии (которая поддерживает async ), либо сохранить все синхронным, кроме уровня пользовательского интерфейса.