2

As best as I can, I opt for async all the way down. However, I am still stuck using ASP.NET Membership which isn't built for async. As a result my calls to methods like string[] GetRolesForUser() can't use async.

In order to build roles properly I depend on data from various sources so I am using multiple tasks to fetch the data in parallel:

public override string[] GetRolesForUser(string username) {
   ...
   Task.WaitAll(taskAccounts, taskContracts, taskOtherContracts, taskMoreContracts, taskSomeProduct);
   ...
}

All of these tasks are simply fetching data from a SQL Server database using the Entity Framework. However, the introduction of that last task (taskSomeProduct) is causing a deadlock while none of the other methods have been.

Here is the method that causes a deadlock:

public async Task<int> SomeProduct(IEnumerable<string> ids) {
    var q = from c in this.context.Contracts

            join p in this.context.Products
            on c.ProductId equals p.Id

            where ids.Contains(c.Id)

            select p.Code;

    //Adding .ConfigureAwait(false) fixes the problem here
    var codes = await q.ToListAsync();
    var slotCount = codes .Sum(p => char.GetNumericValue(p, p.Length - 1));

    return Convert.ToInt32(slotCount);
}

However, this method (which looks very similar to all the other methods) isn't causing deadlocks:

public async Task<List<CustomAccount>> SomeAccounts(IEnumerable<string> ids) {
    return await this.context.Accounts
        .Where(o => ids.Contains(o.Id))
        .ToListAsync()
        .ToCustomAccountListAsync();
}

I'm not quite sure what it is about that one method that is causing the deadlock. Ultimately they are both doing the same task of querying the database. Adding ConfigureAwait(false) to the one method does fix the problem, but I'm not quite sure what differentiates itself from the other methods which execute fine.

Edit

Here is some additional code which I originally omitted for brevity:

public static Task<List<CustomAccount>> ToCustomAccountListAsync(this Task<List<Account>> sqlObjectsTask) {
    var sqlObjects = sqlObjectsTask.Result;
    var customObjects = sqlObjects.Select(o => PopulateCustomAccount(o)).ToList();
    return Task.FromResult<List<CustomAccount>>(customObjects);
}

The PopulateCustomAccount method simply returns a CustomAccount object from the database Account object.

Justin Helgerson
  • 24,900
  • 17
  • 97
  • 124
  • http://stackoverflow.com/questions/12981490/task-waitall-hanging-with-multiple-awaitable-tasks-in-asp-net. It has an explanation too. – Marcel N. Aug 11 '14 at 21:47
  • @MarcelN. - That question specifically says to use `await` which I can't use as I stated in my opening paragraph. This question is different from the one you linked to as my problem only occurs with a specific `Task` out of the many being used. I want to know what it is about that specific method that is causing the problem whereas the other methods are okay. – Justin Helgerson Aug 11 '14 at 22:20
  • @MarcelN. - Yep, I am using the same context across all tasks. I actually was using `WhenAll` originally, but that was deadlocking as well. There is no `Result` property on that return type. – Justin Helgerson Aug 11 '14 at 22:43
  • 1
    I'm certain that Entity Framework doesn't support parallel execution of await-able functions in the same context. In fact, it should throw an exception. – mostruash Aug 11 '14 at 22:55
  • Sorry, I meant `Task.WhenAll(...).Wait()`. Not sure if the linq query counts as an active operation on the context, but most likely not since it's executed when you call `ToListAsync`. – Marcel N. Aug 11 '14 at 22:56
  • @mostruash - The parallel execution of the other four tasks has been in production for months without deadlocking. – Justin Helgerson Aug 11 '14 at 23:00
  • @MarcelN. - `Wait()` results in the same behavior. Also, like I mention in the post, adding `ConfigureAwait(false)` on that fifth task solves the issue. But I'm not quite certain as to why it's an issue for that one method. – Justin Helgerson Aug 11 '14 at 23:02
  • @mostruash - It looks like you're correct, but that only applies to Entity Framework 6 – Justin Helgerson Aug 11 '14 at 23:19
  • @JustinHelgerson I don't think there was a `ToListAsync` prior to EF6. – mostruash Aug 11 '14 at 23:23
  • 4
    @JustinHelgerson EF didn't suddenly drop concurrency support. It never was there. Your use of EF in a concurrent way is invalid and must be fixed. Do you seek an explanation or a solution? Solution is easy: Use ConfigureAsync. Explanation: I have a hunch. Add `await Task.Delay(100);` to SomeAccounts and see if it now deadlocks. It should. And post the definition of ToCustomAccountListAsync. – usr Aug 11 '14 at 23:23
  • @usr - Good comment, thanks for the response. I'm after a solution and explanation. I've updated my question with the definition you asked for. – Justin Helgerson Aug 12 '14 at 21:22

2 Answers2

1

In ToCustomAccountListAsync you call Task.Result. That's a classic deadlock. Use await.

Community
  • 1
  • 1
usr
  • 168,620
  • 35
  • 240
  • 369
0

This is not an answer, but I have a lot to say, it wouldn't fit in comments.

Some fact: EF context is not thread safe and doesn't support parallel execution:

While thread safety would make async more useful it is an orthogonal feature. It is unclear that we could ever implement support for it in the most general case, given that EF interacts with a graph composed of user code to maintain state and there aren't easy ways to ensure that this code is also thread safe.

For the moment, EF will detect if the developer attempts to execute two async operations at one time and throw.

Some prediction: You say that:

The parallel execution of the other four tasks has been in production for months without deadlocking.

They can't be executing in parallel. One possibility is that the thread pool cannot assign more than one thread to your operations, in that case they would be executed sequentially. Or it could be the way you are initializing your tasks, I'm not sure. Assuming they are executed sequentially (otherwise you would have recognized the exception I'm talking about), there is another problem:

Task.WaitAll hanging with multiple awaitable tasks in ASP.NET

So maybe it isn't about that specific task SomeProduct but it always happens on the last task? Well, if they executed in parallel, there wouldn't be a "last task" but as I've already pointed out, they must be running sequentially considering they had been in production for quite a long time.

Community
  • 1
  • 1
mostruash
  • 4,169
  • 1
  • 23
  • 40