Search code examples
c#.netasync-awaittaskn-tier-architecture

which Layer should populate Concurrency and Task in N-Tier architecture pattern in .NET


Please if this question off-topic you can write in a comment to delete it later :)

If i have a 3 Layers ( *.DLL's )

  • DataAccessLayer.DLL
  • BusinessLogicLayer.DLL
  • UI Application ( WPF, WinForms, any technology)

In my application i make BusinessLogicLayer methods return Task and in UI Application i call await when i use a method from BusinessLogicLayer

I have misunderstanding the following:

  • Which layer should support by Task or concurrency ( Back-End, Middle-were, Front )
  • If i need to make reusable BusinessLogicLayer for other developers, where they can also forgot to use awaitable / async methods in UI Application Layer in next projects that handled to them. How to achieve awaitable methods in Middle were layer without need to write await's in every UI Event ( Button_Click, for example ).
  • How to make the DataAccessLayer only contains the concurrency and awaited in UI Layer without needs of using await, async keywords.

My simple code contains the following :

DataAccessLayer.DLL a single method

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 now wrapped the method of dataaccess in simple manner

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

UI Layer ( Any application or front-end provider in .NET )

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

As seen above the DeserializeBuffer method will not execute until the concurrency method ( MoveNext ) is finished.

What i need to make is get rid off await's keywords and async in UI layer.

What i actually do is failed and don't know why this happen

Scenario i try to make as following:

  • Convert the method type of DataAccessLayer from void to 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();
          });
         }
    
  • Then call await in Middle-were layer BusinessLogicLayer

    public async void MoveNext() => await EntryBase.MoveNext();

  • After that calling the MoveNext in UI Layer from Logic layer. i guess that will make it awaitable because it already declared await in middle-were layer. but actually the UI layer executes the next method simultaneously. so the exceptions thrown because off next method ( DeserializeBuffer) depending on previous method ( 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 */
      }
    

Any help would be appreciated.


Solution

  • What i need to make is get rid off await's keywords and async in UI layer.

    This is exactly wrong. The UI layer is the only place where async and await must be used. And they do have to be used in that layer.

    Your data access technology is unspecified, but from the code, I'm guessing it may be DataTable-based, which is problematic because DataTable is extremely old and does not support async. Note that wrapping method bodies with Task.Run to "make them asynchronous" is an antipattern - those are actually fake-asynchronous methods, not truly asynchronous.

    If I'm wrong and your data-access technology does support async, then you should be able to make your DAL methods async without using Task.Run. Start at the lowest level (e.g., whatever methods FetchDataBuffer calls) and change them to the asynchronous equivalents. Then let async grow from there. Note that "letting async grow" means using async Task, not async void; async void in the BLL is definitely an antipattern.

    But if I'm right and your DAL is using DataTable, then you need to decide whether to switch to a newer data-access technology. If this isn't something you can do right now, then I'd recommend keeping the existing DAL and BLL code and just adding async/await to the UI layer:

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

    This is not an antipattern because we're using Task.Run to invoke a method - to move it off the UI thread. It's not ideal, since we're still using more threads than necessary, but an ideal solution would require true asynchronous data access. With this kind of compromise, your DAL and BLL are still blocking, so they have limited use outside of desktop UI applications.