1

I'm getting what I think to be a deadlock when trying to run a bunch of linq queries in parallel.

I am running a Task.WhenAll() on this method:

public async Task<MetabuildScan> GetLatestMetabuildScanAsync(string buildId)
{
    var metabuildScanStatuses = new[] { "Completed", "Referenced" };

    // Get the latest metabuild scan for this image build
    var latestScan = await (from scan in _qsaContext.MetabuildScans
                           where scan.SoftwareImageBuildId == buildId
                           && metabuildScanStatuses.Contains(scan.SIScanStatus)
                           orderby scan.SIScanStartedOn descending
                           select scan).FirstOrDefaultAsync();

    // If there is a related scan, then use that one, else, use the one we just got
    var latestCompletedScanId = latestScan?.RelatedScanId ?? latestScan?.Id;

    return await _qsaContext.MetabuildScans
                 .FirstOrDefaultAsync(scan => scan.Id == latestCompletedScanId);
}

I am getting a System.InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.

_qsaContext was created using Entity-Framework Core.

At first, I thought the FirstOrDefaultAsync would fix my issue (I had a non-asynchronous FirstOrDefault in there at first), but it didn't.

I'm wondering what the best solution to get around this deadlock would be. The table I am selecting from is a large table, so I can't pull the whole table into memory.

Window
  • 1,377
  • 2
  • 13
  • 23

2 Answers2

0

As usual, wrap your query in try/catch and repeat your transaction.

Sergei Zinovyev
  • 1,238
  • 14
  • 14
  • That catches the exception but it doesn't solve my problem. Updating answer with the exception in hopes that it helps. – Window Jul 07 '18 at 00:38
  • Some time it is hard to avoid deadlocks in large multithread/multiprocess systems. Catch and re-try is the simplest solution. Another way is to use NOLOCK. – Sergei Zinovyev Jul 07 '18 at 00:41
  • If catching and retrying is the only solution then there is no point in having this code be asynchronous. Catching and retrying will also add a lot of overhead with all of the retry attempts. This method I am working with is run almost always on 10 different threads at the same time. That's a lot of overhead. – Window Jul 07 '18 at 00:45
  • Is NOLOCK with LINQ a good solution? I've seen https://stackoverflow.com/questions/1220807/nolock-with-linq-to-sql but the solutions don't look clean, they look like hacks. – Window Jul 07 '18 at 00:47
  • The DB does not care about sync/async calls. It received requests and the deadlock just happened. Async is the right way to go if you have many threads and you want your OS to start/exec/finish more threads at a time. Try/catch does not add any additional expensive overhead to your code. Repeating transaction may not a good solution for banks where data may change in quick way. In you case you work with some queue stored in a table. So, it should be fine. – Sergei Zinovyev Jul 07 '18 at 00:52
  • NOLOCK is the same as READ_UNCOMMIT. I usually do not recommend it. But if you want to try then there should be like this: ``` using (var tr = _qsaContext.BeginTransaction(IsolationLevel.ReadUncommit)) { // your EF LINQ here } ``` – Sergei Zinovyev Jul 07 '18 at 00:53
0

Entity Framework DbContext is not thread safe. You can't run parallel queries against it like you are attempting to do.

If you need to have a shared context for your queries that you'll need to await each one individually and sequentially.

If they don't need a shared context but do need to run in parallel then you'll need to have a separate context for each query.

If using a DI framework, perhaps you can look into making your DbContext Transient (instead of what I'm assuming is Scoped) and injecting that into a query class that will call your query methods.

mcbowes
  • 798
  • 1
  • 6
  • 14