1

After going through a lot of articles and videos i am still having problem with the asynchronous programming.I am working on a project where in service layer I have created all the methods as async. All of the return Task <T> or Task (I made sure not to return void). Now to the issue. My Api calls async methods which internally calls other async methods which may even call other async methods. So I await every time I encounter an async method. The downside with this approach, I think, is that cause I am awaiting for result every time I encounter async it will take a lot of time. For ex:

public async Task<xyz> DoMainTask(int ActionId, int ItemId, int UserId)
{
   await DoTask1(ActionId,ItemId,UserId);  3 sec
   await DoTask2(ActionId,ItemId,UserId);  3 sec
   await DoTask3(ActionId,ItemId,UserId);  3 sec
}

So I don't want to wait for 9 sec because all tasks here are independent of each other. I want to do something like:

public async Task<xyz> DoMainTask(int ActionId, int ItemId, int UserId)
{
   List<Task> lst = new List<Task>();
   t1= DoTask1(ActionId,ItemId,UserId);  
   lst.Add(t1);

   t2 = DoTask2(ActionId,ItemId,UserId);
   lst.Add(t2);

   t3 = DoTask3(ActionId,ItemId,UserId);
   lst.Add(t3);

   await Task.WhenAll(lst);

   // do some work
   return xyz;
}

Which will probably take around 5-6 sec. How do I do this? Whenever I try to use 2nd approach it get error:

A second operation started on this context before a previous asynchronous operation completed

DoTask1 is defined as:

  public async Task DoTask1 (int ActionId, int ItemId, int UserId)
    {
        try
        {
            DailyActivityPoint dailyActivityPoint = new DailyActivityPoint()
            {
                ActionId = ActionId,
                CreatedDate = DateTime.Now,
                ItemId = ItemId,
                UserId = UserId,
                PointsAccumulated = await GetPointsAwardedForAction(ActionId)
            };

            _entities.DailyActivityPoints.Add(dailyActivityPoint);
            await _entities.SaveChangesAsync();
        }
        catch (Exception ex)
        {

        }
    }

inside DoTask1 I am also calling an async method.

How can it be done and what's the best practice?

Cœur
  • 37,241
  • 25
  • 195
  • 267
  • Use async/await for non cpu bound stuff like I/O and use threads for cpu bound stuff like bigger/longer calculations. – user743414 Jun 01 '18 at 09:29
  • I can't pull up my Visual Studio right now to help ya, but definitely put an await in front of that `Task.WhenAll` – Malachi Jun 01 '18 at 09:30
  • 1
    that I did. Forget to put here :p I'll edit the post. – user1063760 Jun 01 '18 at 09:31
  • Also what environment are you running this in? Standard desktop .NET? I think https://www.pmichaels.net/tag/a-second-operation-started-on-this-context-before-a-previous-operation-completed/ probably describes your issue - are you using EF? – Malachi Jun 01 '18 at 09:34
  • 2
    Your problem is that you cannot reuse an Entity Framework in multiple tasks. You will need to create a new dbcontext per task. See https://stackoverflow.com/questions/36036401/ef6-two-contexts-vs-single-context-with-two-awaits – Peter Bons Jun 01 '18 at 09:44
  • 1
    Yes. I am working on an WebAPI2 project using EF. The solution given in the article you shared is the solution to the problem where user was using IOC. I am not. At least not here in this module . Didn't created inface for DbContext, Only for the services i created. Although i have used autofac in different module belonging to the same prject but don't think it will affact it in any way. Do i need to create my subMethods as Task insted of async Task. – user1063760 Jun 01 '18 at 09:46

2 Answers2

1

I believe you are encountering a thread-safety concern as described here. If so, you'll need to be sure that for each 'awaitable' that reaches into EF it is using its own DbContext instance.

So, be sure you aren't using a DbContext singleton; instantiate a new one as you go if you can or get tricky with a container like he does in the link (containers are your friends)

Malachi
  • 2,260
  • 3
  • 27
  • 40
  • 1
    Ahh Therad safety...Ok. Thanks for the help. I will try keeping these things in mind :) – user1063760 Jun 01 '18 at 09:50
  • Guessing from your code snippet, your `_entities` appears to be directly your `DbContext`, so probably that's acting like a singleton. If you create and destroy a local `_entities` within your function, this might do the trick. – Malachi Jun 01 '18 at 10:04
  • 1
    Yeah .. it did the trick. Created context inside the methods rather than using shared context and it worked fine... Thanks :) – user1063760 Jun 01 '18 at 10:46
  • Glad to help out! – Malachi Jun 01 '18 at 10:53
0

Maybe you should use this guideline in writing your asynchronous methods

Guideline:

  1. Write the method the normal way you would write a conventional method, then convert it to an async method.

  2. Use the async keyword in the method declaration.

    public async Task<int> ExampleMethodAsync()  
    {  
        // . . . .  
    } 
    
  3. Use the await keyword in the code calling an asynchronous process/method.

      int resultValue = await ExampleMethodAsync();
    

Note: await can only be used in an async method modified by the async keyword.

  1. Use one of the correct return types in the async method in order to get results

    return types for an async method need to be one of the following:
    
    • Task if your method has a return statement in which the operand has type TResult.
    • Task if your method has no return statement or has a return statement with no operand.
    • void if you're writing an async event handler.
  2. Adding the “Async” suffix at the end of the calling method name. This is not required but is considered a convention for writing async methods in C#.

    public async Task<int> ExampleCallingMethodAsync()  
    {  
        // . . . .  
    }
    
  3. Include at least one await expression in the async method code. The suspension of an async method at an await expression doesn't constitute an exit from the method, and finally blocks don’t run.

    public async Task<int> ExampleMethodAsync()  
    {  
     //some code
     string pageContents = await client.GetStringAsync(uri);   
     //some more code
     return pageContents.Length;
    }
    

    Note:

    1. You have to adjust what your async method is returning. The object being returned has to match the type Task<T> of your async method
    2. If an async method doesn’t use an await operator to mark a suspension point, the method executes as a synchronous method does, despite the async modifier. The compiler issues a warning for such methods.

Below is a typical method converted to an asynchronous method

Example

    private void WebPage(string someURI) 
 { 
    WebClient webClient = new WebClient(); 
    string pageContent = webClient.DownloadString(someURI);
    Console.WriteLine(pageContent);
 }

changed to:

   private async void WebPageAsync(string someURI)
 { 
  WebClient webClient = new WebClient();
  string pageContent = await webClient.DownloadStringTaskAsync(someURI); 
  Console.WriteLine(pageContent); 
 }

I hope this was helpful?

Krypt1
  • 1,066
  • 8
  • 14