Best practices for modifying collections in asynchronous programming using async/await are:

Asked 2 months ago, Updated 2 months ago, 3 views

I have a scene graph of a tree structure in my own game.
The parent node has multiple child nodes, and as the game progresses, nodes are added or deleted.Hello is a change to the collection.Asynchronous programming using async/wait may cause an exception, depending on the timing, to change the collection from another thread while spinning in foreach.

The question is, what is the best way to operate the collection with asynchronous programming?

I can't decide whether it's right or wrong.Please let me know what you think.

Add considering your answers

4. includes ConcurrentBag, ConcurrentStack, ConcurrentQueue, ConcurrentDictionary, etc.The call method is significantly different than the existing list.The Producer-Consumer pattern is based on queues and stacks.
5. is a library provided by Microsoft that is not a .Net standard.Installable with NuGet.It's an immutable collection, so there's no problem in a multi-threaded environment.Collection changes are relatively expensive because they are copied and replaced with changes.There is no problem with just browsing.

The ImmutableList number 5 is the best, considering that exclusive control does not appear in the code, that it has an interface similar to the existing list, and that rewriting is negligible because it does not occur that often.
It's not a .Net standard at the moment, but I think it's going to be incorporated as a standard.This is convenient.

c# game-development

2022-09-30 13:52

4 Answers

Use the System.Collections.Concurrent namespace collection.
Collections belonging to this namespace are asynchronously accessible simultaneously.
There is no need to lock.

Another way to do this is to
attribute to the System.Collections.Impossible namespace. Use an invariant collection.
Changes to a collection of invariant collections create references to the new collection.

System.Collections.Impossible may not be available as a standard.
It can be deployed via nuget.

These collections allow you to program lock-free.
Changing a collection during an enumeration operation does not result in an exception.

2022-09-30 13:52

If Read > Write frequency to the collection is Read > Write, then

  • System.Threading.ReaderWriterLockSlim

You may want to consider using .
However, since the overhead is larger than that of a typical Monitor Lock, for example, a Wirte>Read frequency can be more efficient.


I would like to add that the priority is often considered.

2022-09-30 13:52

The answer is based on the assumption that the tree structure scene graph (hereinafter referred to as the tree) must be consistent.
If not, I think it is sufficient to use the System.Collections.Concurrent or System.Collections.Impossible collection (hereinafter referred to as the lock-free collection).

Normally, you cannot use the lock statement freely if you use the async/away pattern, but one solution is to use using.

Asynchronous: How do I lock a code that contains wait? (AsyncLock Edition)
Building Async Coordination Primitives, Part 6: AsyncLock

Select here if you want to use ReaderWriterLock.
Building Async Coordination Primitives, Part 7: AsyncReaderWriterLock

AsyncLock Usage Case

readonly AsyncLock_lock=new AsyncLock(); 

async HogeAsync()
  using(var release=wait_lock.LockAsync()) 
    // You can use await among them.
    wait this.FooAsync(); 

Benefits of this method

Any operation can be atomic, so the tree structure can be consistent at all times.
Even if you have multiple update threads, you can keep them consistent as long as they are locked correctly.

Disadvantages of this method

AsyncLock is the same as SemaphoeSlim and is not designed to be re-entered.
In the example code above, if you call LockAsync within the FooAsync method, it will be dead-locked.
Less smart ingenuity is required, such as having a lock argument or being careful not to lock only certain methods.

Prepare two trees: an update tree and a read tree.
The foreach reader looks at the snapshot tree.
You update the tree against the update tree.
When the update is complete, clone the entire update tree and use the Interlocked.Exchange method to replace the copied tree with the read tree.

Benefits of this method

Allows consistency of the read tree structure.
You don't need to lock, so you don't have to worry about deadlock.

Disadvantages of this method

If the tree has a large number of nodes, it consumes a large amount of memory, and the clone operation is a burden to the CPU.
Also, if you have multiple update threads and update them at the same time, and the update tree remains inconsistent after all of these updates have been completed, the read tree that you copied will also become inconsistent.

For example, if a node swap operation is performed asynchronously while reading a tree, the node appears to the reader to suddenly disappear or the same node appears twice.
Think of it as something similar to the Phantom Read phenomenon in RDB.

Another example is to add new N nodes as children of different parent nodes at the same time.
This operation cannot be atomic in the lock-free collection.
It does not appear to the reader to have been "added at the same time."

If you don't have game logic for this situation, you'll find it very difficult to debug because it's not reproducible and confusing bugs and looks bad.

2022-09-30 13:52

If I were you, I'd look like this

  • Limit one thread to update
  • Separate the drawing tree from the tree you are creating and do not change it
  • If there are more than one update object, limit the updateable range of each object.Combine data into logical trees for role sharing and draw trees from each logical node

2022-09-30 13:52

If you have any answers or tips

© 2022 OneMinuteCode. All rights reserved.