1

I have this code to retrieve data from my database:

public async Task<IEnumerable<Member>> GetAllMembersAsync()
{
    var members = Repository.GetAll<Member>();

    return await Task.Run(() => members.ToListAsync());
}

For an unknown reason (maybe a bug?), I have to use Task.Run in order to make this work (the repository just returns a DbSet<Member>. If I don't do this, my UI hangs forever.

The problem is that I can't do 2 database operations at the same time this way. If I do this, I get this error:

A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

As you see, I'm already using await.

Is there any way to solve this so I can actually do 2 database operations at the same time and so they run in order?

EDIT: Code that calls this:

private async void LoadMembers()
{
    try
    {
        var members = await MemberService.GetAllMembersAsync();

        Members = new ObservableCollection<Member>(members);
    }
    catch (EntityException)
    {
        // connection lost?
    }

}
Bv202
  • 3,924
  • 13
  • 46
  • 80
  • Can you show the code that is calling your `GetAllMembersAsync()` method? – rory.ap Feb 07 '15 at 13:34
  • How using a `.ContinueWith` for the second async call? You'd have to kick off another task in the continuation, but at least these tasks will be daisy-chained. So many ways to skin this cat, though... – code4life Feb 07 '15 at 13:37
  • The problem is I don't know what the next async call is - the user could decide to do multiple things. It could be retrieiving all members again, or doing something completely different; – Bv202 Feb 07 '15 at 13:38
  • Then keep the reference to the last task being run, and if the user keeps appending data fetch ops, add these in as continuations. Just don't throw away your reference to the last task run. – code4life Feb 07 '15 at 13:39
  • possible duplicate of [Mutil async entity framework 6?](http://stackoverflow.com/questions/20628792/mutil-async-entity-framework-6) – dotnetom Feb 07 '15 at 13:53
  • What exactly is the second operation because your code shows only one. Getting all members. – Philip Stuyck Feb 07 '15 at 13:59
  • The second operation in this case is getting all members 2 times. But it could be something completely different.. – Bv202 Feb 07 '15 at 14:01

2 Answers2

2

The answer is no. As the error suggests you can't have 2 concurrent EF operations at the same time. Here's a more thorough answer about that: Does Entity Framework support parallel async queries?

On top of that, you don't need to use Task.Run to solve what seems to be a deadlock issue. Simply use ConfigureAwait to make sure the async operation doesn't need the UI SynchronizationContext to complete on. (also, make sure you don't block on async code with Task.Wait or Task.Result):

public async Task<IEnumerable<Member>> GetAllMembersAsync()
{
    return await Repository.GetAll<Member>().ToListAsync().ConfigureAwait(false);
}

private async void LoadMembers()
{
    try
    {
        var members = await MemberService.GetAllMembersAsync();
        Members = new ObservableCollection<Member>(members);
    }
    catch (EntityException)
    {
        // connection lost?
    }
}
Community
  • 1
  • 1
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • So what about this: I create a Singleton called SIngleTask to use for database operations. This Singleton makes sure only one Task is active. If not, it uses ContinueWith. Is this a good option? – Bv202 Feb 07 '15 at 14:58
  • @Bv202 There are simpler ways to serialize `async` operations. You can simply use a `SemaphoreSlim.WaitAsync` configured with 1. – i3arnon Feb 07 '15 at 15:19
  • I'd solve the deadlock by removing the wait that surely is hidden somewhere. ConfigureAwait(false) is the wrong treatment. – usr Feb 07 '15 at 15:30
  • @Bv202 @usr, you should do both. And that's unrelated to the concurrent `async` EF operations. – i3arnon Feb 07 '15 at 15:32
  • Thanks a lot for the tip of using SemaphoreSlim! That seems to do exactly what I want! – Bv202 Feb 07 '15 at 17:01
1

I think you are in a deadlock situation because you are not using configureawait(true) properly. You can do

public async Task<IEnumerable<Member>> GetAllMembersAsync()
{
   var members = Repository.GetAll<Member>();

   return await Task.Run(() => members.ToListAsync());
}

private async void LoadMembers()
{
   try
   {
       var members = await MemberService.GetAllMembersAsync().ConfigureAwait(true);

       Members = new ObservableCollection<Member>(members);
   }
   catch (EntityException)
   {
    // connection lost?
   }

}

after the operation is complete, and if executed from the GUI thread, the GUI thread will resume.

Philip Stuyck
  • 7,344
  • 3
  • 28
  • 39
  • Thanks a lot, this seems to do the trick! Why do I need to use this extra method? Shouldn't that be the default behavior? – Bv202 Feb 07 '15 at 13:41
  • var members = await MemberService.GetAllMembersAsync().ConfigureAwait(true); is more correct. The reason is that your observablecollection is next in line and is used in binding operations on the GUI thread. That in combination with var list = await members.ToListAsync().ConfigureAwait(false) also works. I like to explicitely do these calls to configureawait even when not needed, there is a default, because it shows intent. If this solves your problem please mark as answer ;-) – Philip Stuyck Feb 07 '15 at 13:44
  • Unfortunately, it makes my UI hang while retrieving the data... :( – Bv202 Feb 07 '15 at 13:47
  • 1
    The answer is nice, but is not for the question that was asked. The question was how to run multiple database operations on the same context at the same time. – dotnetom Feb 07 '15 at 13:52
  • Unfortunately, it doesn't work. I'm still getting the same error: operation on context already running... – Bv202 Feb 07 '15 at 13:53
  • If that is the question then the answer is that it is impossible using the same context. I thought the idea of the requester was to decouple the from the GUI thread. But multiple queries have the be executed one by one, if using the same context. – Philip Stuyck Feb 07 '15 at 13:54
  • So I'll have to create a new context for EVERY database operation? – Bv202 Feb 07 '15 at 13:56
  • Only if you want to run them in parallel. – Philip Stuyck Feb 07 '15 at 13:56
  • My situation: the user clicks on a menu item, new data is loaded. The user then clicks on another menu item and new data is loaded, while the previous load may not be completed. How to implement this the best way? – Bv202 Feb 07 '15 at 13:58
  • You would need to cancel the previous query before attempting the new one. Or use a new context with each click. Cancelling can be done using a CancellationSource and token. See https://msdn.microsoft.com/en-us/library/dd997364%28v=vs.110%29.aspx – Philip Stuyck Feb 07 '15 at 14:01
  • I can't just cancel these queries - now it's just loading, but it could also be adding, deleting,... Creating a new context every time seems to slow down the applicatio a LOT... – Bv202 Feb 07 '15 at 14:07