Runtime & concurrency
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Reflection;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
var first = new Account(1, 3);
var second = new Account(2, 2);
Transfer(first, second, 1); Transfer(second, first, 1);
Console.WriteLine($"a:{first.Balance}");
Console.WriteLine($"b:{second.Balance}");
Console.WriteLine($"total:{first.Balance + second.Balance}");
static void Transfer(Account from, Account to, int amount)
{
if (amount < 1 || from.Id == to.Id) throw new ArgumentException("Distinct ordered accounts and positive amount required");
Account low = from.Id < to.Id ? from : to, high = from.Id < to.Id ? to : from;
lock (low.Sync) lock (high.Sync) { if (from.Balance < amount) throw new InvalidOperationException(); from.Balance -= amount; to.Balance += amount; }
}
sealed class Account(int id, int balance) { public int Id { get; } = id; public object Sync { get; } = new(); public int Balance = balance; }
Reveal answer and reasoning
The output is a:3, b:2, and total:5. The accounts begin at three and two. Transferring one from first to second makes them two and three, then the reverse transfer restores three and two. Both operations acquire the lower identifier first, even though their source and destination roles differ. The observed sum remains five in this bounded successful sequence.
The code uses actual nested monitors but creates no worker threads in this portable snippet. A common wrong trace leaves the balances at two and three by overlooking the second transfer, or predicts deadlock solely because transfer direction reverses. The separate desktop project adds real opposing workers with larger bounded balances. Neither trace establishes database durability, thread safety for arbitrary direct field writes, or a complete arithmetic failure policy.
Final totals alone also would not detect every possible incorrect distribution between the accounts.
What a strong answer covers
- The reverse transfer restores the starting balances.
- Acquisition order stays lower identifier first.
- The portable sequence is not a concurrency stress test.
Common pitfall
Confusing operation direction with lock order or omitting one of the two transfers.
Follow-up questions
- What is printed after only the first transfer?
- Why is checking only total weaker than checking both balances?
Self-review
Choose what to revisit. These marks are your own assessment and are separate from lesson completion.