# Async Operations & Locking

Since the graph database handles high-concurrency workloads, many operations are asynchronous to avoid blocking threads.

# Async Enumeration

If you are processing a large result set, consider using AsEnumerableAsync(). This method parallelizes the reading of data from disk, which can significantly improve performance for heavy queries.

await foreach (var node in Graph.Query().StartAt("Log").AsEnumerableAsync())
{
    // Process one by one without buffering everything in memory
    await ProcessLogAsync(node);
}

# Locking and Thread Safety

When modifying the graph, you must use async methods to acquire locks. This ensures thread safety and prevents data corruption.

# The Locking Pattern

To modify a node, you must follow this specific pattern:

  1. Acquire Lock: Use TryGetLockedAsync or GetOrAddLockedAsync to get a LockedNode.
  2. Modify: Perform your updates on the LockedNode object.
  3. Commit: Call CommitAsync to save changes and release the lock.
// 1. Acquire Lock
var lockedNode = await Graph.TryGetLockedAsync(someUID);

if (lockedNode != null)
{
    try
    {
        // 2. Modify
        // Perform logic that might throw exceptions here
        lockedNode.UpdateProperty("visited", true);

        // 3. Commit
        await Graph.CommitAsync(lockedNode);
    }
    catch (Exception)
    {
        // If something goes wrong, abandon changes to release the lock
        Graph.AbandonChanges(lockedNode);
        throw;
    }
}

# LockedNode vs ReadOnlyNode

  • ReadOnlyNode: Lightweight, safe for reading. Returned by queries.
  • LockedNode: Heavyweight, represents exclusive access to a node for modification.

# CommitAsync

The CommitAsync method pushes changes to the storage engine and releases the lock.

// Commit single node
await Graph.CommitAsync(node);

// Commit multiple nodes (atomically)
await Graph.CommitAsync(nodeA, nodeB);

If you modify a node but decide not to save (e.g., validation failed), use AbandonChanges(node).