1

We are using .NET Core 3.1 with MVC architecture. We have a simple view (Index.cshtml file) which renders a list of in-memory items returned from the singleton service. This is why we need locking mechanism - one thread can add items to the list while they are still being looped over with foreach in Index.cshtml. If this occurs, the exception Collection was modified; enumeration operation may not execute is thrown.

Index.cshtml

@{
    var lockObj = new object();
    var collection = Enumerable.Range(1, 10); // returned from the singleton service
}

@lock (lockObj)
{
    @foreach (var item in collection)
    {
        <a asp-controller="Home" asp-action="Index" asp-route-id="@item">
            @item
        </a>
    }
}

I am aware that code above doesn't make any sense (lockObj needs to be shared among all application layers, it shouldn't be initialized in the view), but it clearly reproduces the build error that I get. Once I build the solution, compiler throws the following error:

CS1996 Cannot await in the body of a lock statement

If I remove asp-* tag helpers, the code compiles successfully. What am I doing wrong? Do asp-* tag helpers use async methods under the hood? Can I use asp-* tag helpers inside lock statement?

Sam Carlson
  • 1,891
  • 1
  • 17
  • 44
  • Does this answer your question? [Why can't I use the 'await' operator within the body of a lock statement?](https://stackoverflow.com/questions/7612602/why-cant-i-use-the-await-operator-within-the-body-of-a-lock-statement) – pfx Dec 05 '22 at 08:23
  • @pfx I understand that I can't use `await` operator inside the body of the `lock` statement. However, I am not using any `await` operator **explicitly** in my code. If it is used, it must be used behind-the-scenes by `asp-*` tag helpers. – Sam Carlson Dec 05 '22 at 08:25
  • 2
    Why do you want to lock the html part anyways? – kingrazer Dec 05 '22 at 08:43
  • 1
    Depending on the size of your list, copying it with a call to `ToList()` might be the easiest way. No need to lock a collection that will only exist for your view. – Irwene Dec 05 '22 at 09:05
  • @kingrazer I explained why I want to do that in the question. Singleton service `TestService` maintains an in-memory list of objects (`TestList`). It mutates `TestList` by adding and deleting entries. This same in-memory `TestList` is used in `Index.cshtml`. It can happen that while I am in the middle of looping through the list in `Index.cshtml`, an item gets added to the `TestList` by `TestService`. If this occurs while loop in view is still in progress, an exception is thrown (`Collection was modified; enumeration operation may not execute`). – Sam Carlson Dec 05 '22 at 09:07
  • Would you be able to add the lock block inside the singleton service? Or could you use a thread-safe collection? – juunas Dec 05 '22 at 12:01
  • I'm guessing the serializer for the asp markup is using some version of `Response.WriteAsync` under the covers. To solve your problem I would create a copy of the mutable data in the controller (within a `lock` block) and pass the copy to the view in the model. – John Wu Dec 05 '22 at 21:21

1 Answers1

1

Do asp-* tag helpers use async methods under the hood?

Possibly.

Can I use asp-* tag helpers inside lock statement?

No.

What am I doing wrong?

You don't want to use lock here - or anywhere where there could be asynchronous code. In ASP.NET Core, this includes a lot of subcomponents; in ASP.NET pre-core, child actions and similar sub-components must be invoked synchronously, and ASP.NET Core fixed that.

For a proper solution, I would recommend using a concurrent or immutable collection. These generally have "snapshot" semantics, so your view code can iterate over the items without blocking any updates. But if you prefer, you can just use SemaphoreSlim instead of lock for an async-friendly lock.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810