在 C# 中使用 lock 语句

Using lock statement in c#

我需要使用锁构造,编辑以下方法并行执行:

    public void Withdraw(int amountToWithdraw)
            {
                if (amountToWithdraw <= 0)
                {
                    throw new ArgumentException("The amount should be greater than 0.");
                }
    
                if (amountToWithdraw > MaxAmountPerTransaction)
                {
                    throw new ArgumentException($"The value {amountToWithdraw} exceeds transaction limit: {MaxAmountPerTransaction}.");
                }
    
                if (amountToWithdraw > Amount)
                {
                    throw new ArgumentException("Insufficient funds.");
                }
                
                WithdrawAndEmulateTransactionDelay(amountToWithdraw);
    
            }

这是结果

private readonly object balanceLock = new object();
public void Withdraw(int amountToWithdraw)
        {
            if (amountToWithdraw <= 0)
            {
                throw new ArgumentException("The amount should be greater than 0.");
            }

            if (amountToWithdraw > MaxAmountPerTransaction)
            {
                throw new ArgumentException($"The value {amountToWithdraw} exceeds transaction limit: {MaxAmountPerTransaction}.");
            }

            if (amountToWithdraw > Amount)
            {
                throw new ArgumentException("Insufficient funds.");
            }

            lock (balanceLock)
            {
                WithdrawAndEmulateTransactionDelay(amountToWithdraw);
            }

        }

这是对方法 WithdrawAndEmulateTransactionDelay 的描述,不应更改

private void WithdrawAndEmulateTransactionDelay(int amountToWithdraw)
        {
            Thread.Sleep(1000);
            Amount -= amountToWithdraw;
        }

但是,单元测试失败了。我的代码哪里出错了?

看来,您应该将最后的验证 放在 锁中:在您当前的实现中,可能

  1. 线程 #1 尝试撤回 cash1,这是有效的 (cash1 < Account),验证已通过
  2. 线程 #2 尝试撤回 cash2,这是有效的 (cash2 < Account),验证已通过
  3. 不过cash1 + cash2 > Account
  4. 线程 #1 调用 WithdrawAndEmulateTransactionDelay,现在 Amount == Amount - cash1 < cash2
  5. 线程 #2 调用 WithdrawAndEmulateTransactionDelay;因为 Amount - cash1 < cash2 你的测试失败了
private readonly object balanceLock = new object();

public void Withdraw(int amountToWithdraw) {
  // These validations are not depended on Amount, they don't want lock
  if (amountToWithdraw <= 0)
    throw new ArgumentOutOfRangeException(nameof(amountToWithdraw), 
      "The amount should be greater than 0.");

  if (amountToWithdraw > MaxAmountPerTransaction)
    throw new ArgumentOutOfRangeException(nameof(amountToWithdraw), 
      $"The value {amountToWithdraw} exceeds transaction limit: {MaxAmountPerTransaction}.");

  // from now on we start using Amount, so we need the lock:
  lock (balanceLock) {
    if (amountToWithdraw > Amount)
      throw new ArgumentException("Insufficient funds.", nameof(amountToWithdraw));

    WithdrawAndEmulateTransactionDelay(amountToWithdraw);
  } 
}

我也会避免所有这些例外情况。错误的输入是此代码通常是意料之中的,因此并非例外。

试试这个代码:

public TransactionStatus Withdraw(int amountToWithdraw)
{
    bool successful = false;
    string message = "OK";
    int balanceBefore = Amount;
    int balanceAfter = Amount;
    if (amountToWithdraw <= 0)
    {
        message = "The amount should be greater than 0.";
    }
    else if (amountToWithdraw > MaxAmountPerTransaction)
    {
        message = $"The value {amountToWithdraw} exceeds transaction limit: {MaxAmountPerTransaction}.";
    }
    else
    {
        lock (balanceLock)
        {
            if (amountToWithdraw > Amount)
            {
                message = "Insufficient funds.";
            }
            else
            {
                Thread.Sleep(1000);
                Amount -= amountToWithdraw;
                successful = true;
                balanceAfter = Amount;
            }
        }
    }
    return new TransactionStatus()
    {
        Successful = successful, Message = message, BalanceBefore = balanceBefore, BalanceAfter = balanceAfter
    };
}

public struct TransactionStatus
{
    public bool Successful;
    public string Message;
    public int BalanceBefore;
    public int BalanceAfter;
}