如何在第二个方法完成时执行代码

How to execute code when the second method completes

提问人:David Thielen 提问时间:11/11/2023 最后编辑:David Thielen 更新时间:11/13/2023 访问量:94

问:

我有一种情况,我需要在 2 个方法中的 1 个结束时执行一些代码 - 以完成第二个方法为准。我的特殊情况是 Blazor 服务器,我需要在 OnInitializedAsync()/ OnAfterRenderAsync() 结束时执行此代码。在这种情况下,代码依赖于 JavaScript 的可调用性,以及页面模型是否完全从数据库中填充。

我想出了以下课程来实现这一目标:

public class DoubleFinish
{
    private volatile bool _firstFinished = false;
    private volatile bool _secondFinished = false;
    private volatile bool _alreadyReturnedTrue = false;

    /// <summary>
    /// Call when the first method completes. Returns true if the 2nd method is also
    /// complete and so this method can now run the code that requires both methods.
    /// </summary>
    public bool TryFirstFinished
    {
        get
        {
            lock (this)
            {
                _firstFinished = true;
                if (_alreadyReturnedTrue || ! _secondFinished)
                    return false;
                _alreadyReturnedTrue = true;
                return true;
            }
        }
    }

    /// <summary>
    /// Call when the second method completes. Returns true if the 1st method is also
    /// complete and so this method can now run the code that requires both methods.
    /// </summary>
    public bool TrySecondFinished
    {
        get
        {
            lock (this)
            {
                _secondFinished = true;
                if (_alreadyReturnedTrue || ! _firstFinished)
                    return false;
                _alreadyReturnedTrue = true;
                return true;
            }
        }
    }
}

然后,我在我的razor.cs文件中具有以下内容(它们所在的两种方法可以在不同的任务中执行,因此可以同时执行):

OnInitializedAsync() {
    // ... lots of DB access
    if (DoubleFinish.TryFirstFinished)
        await OnAfterInitializeAndRenderAsync();
}

OnAfterRenderAsync(bool firstRender) {
    if (!firstRender)
        return;
    if (DoubleFinish.TrySecondFinished)
        await OnAfterInitializeAndRenderAsync();
}

我有两个问题:

  1. 我需要在 中声明布尔值吗?DoubleFinishvolatile
  2. 是否有某种原子调用来检查/设置布尔值,从而避免使用 ?lock

更新:一个主要制约因素。 在某些配置中可以调用两次。所以我不能使用计数器。我必须专门跟踪每种方法是否已完成。OnInitializedAsync()

C# 多线程 线程安全

评论

0赞 Guru Stron 11/11/2023
你真正想实现什么?你有一个完整的最小可重复的例子吗?
0赞 Jonathan Dodds 11/11/2023
布尔斯不是.“是否有某种原子调用来检查/设置布尔值......?”请参阅 Interlocked 类。从要避免的 lock 语句中。volatilelock (this)
0赞 David Thielen 11/11/2023
@JonathanDodds 首先,感谢您不要使用“lock(this)”——我不知道这一点。在 Interlocked 类中,我没有看到使用它处理 3 个布尔值的方法。我有一个可以调用两次的约束,所以我不能使用计数器。谢谢OnInitializedAsync()
0赞 David Thielen 11/11/2023
@JonathanDodds 布尔值不波动是什么意思?
1赞 David Thielen 11/13/2023
@JonathanDodds我发现关于这个问题的讨论非常有教育意义。有趣的是,一些“小”问题最终变得非常有教育意义。

答:

1赞 Theodor Zoulias 11/11/2023 #1

我认为使用 Interlocked.Decrement 方法并以原子方式减少整数计数器会更简单。当计数器下降到零时,可以确定最后一个挂起的操作已完成:

private int _pendingCount = 2;

OnInitializedAsync()
{
    // ...
    if (Interlocked.Decrement(ref _pendingCount) == 0)
        await OnAfterInitializeAndRenderAsync();
}

OnAfterRenderAsync()
{
    // ...
    if (Interlocked.Decrement(ref _pendingCount) == 0)
        await OnAfterInitializeAndRenderAsync();
}

更新:如果这两种方法中的每一个都可以多次调用,则它会变得更加复杂,但您仍然可以使用 Interlocked 类。以下是该类型的原子按位 OR 运算的实现:uint

/// <summary>
/// Returns the result of an atomic bitwise OR operation.
/// </summary>
static uint AtomicBitwiseOr(ref uint location, uint operand)
{
    uint oldValue = location;
    while (true)
    {
        uint newValue = oldValue | operand;
        uint original = Interlocked.CompareExchange(ref location, newValue, oldValue);
        if (original == oldValue) return newValue;
        oldValue = original;
    }
}

它可以这样使用:

private uint _state = 0;

OnInitializedAsync()
{
    // ...
    if (AtomicBitwiseOr(ref _state, 0b1) == 0b11)
        await OnAfterInitializeAndRenderAsync();
}

OnAfterRenderAsync()
{
    // ...
    if (AtomicBitwiseOr(ref _state, 0b10) == 0b11)
        await OnAfterInitializeAndRenderAsync();
}
1赞 Guru Stron 11/11/2023 #2

您可以做的一件事是使用 Interlocked.Increment

private int counter = 2;

OnInitializedAsync()
{
    // ...
    if (Interlocked.Increment(ref counter) == 2)
        YourCodeToRun();
}

OnAfterRenderAsync(bool firstRender)
{
    // ...
    if (Interlocked.Increment(ref counter) == 2)
        YourCodeToRun();
}

AsyncCountdownEvent。类似于以下内容(如果在 Blazor 上下文中可行):SemaphoreSlimNito.AsyncEx.Coordination

private AsyncCountdownEvent signal = new AsyncCountdownEvent(2);

Task.Run(async () =>
{
    await signal.WaitAsync();
    YourCodeToRun();
})

OnInitializedAsync()
{
    // ...
    signal.Signal();
}

OnAfterRenderAsync(bool firstRender)
{
    // ...
    signal.Signal();
}

VPg公司

如果可以多次运行,那么您的解决方案应该没问题。请注意,您应该避免使用 ,只需创建一个用于锁定的字段,并且不需要 volatile,因为您仅在锁内使用布尔值。OnInitializedAsynclock(this)private object _locker = new object();

或者你可以玩这样的怪物(尽管我会坚持锁定):

private int flag1 = 0;
private int flag2 = 0;
private int hasRun = 0;

OnInitializedAsync()
{
    // ...
    Interlocked.Exchange(ref flag1, 1);
    if(Interlocked.Read(ref flag1) + Interlocked.Read(ref flag2) == 2 && Interlocked.Exchange(ref hasRun, 1) == 0)
        YourCodeToRun();
}

OnAfterRenderAsync(bool firstRender)
{
    // ...
    Interlocked.Exchange(ref flag2, 1);
    if(Interlocked.Read(ref flag1) + Interlocked.Read(ref flag2) == 2 && Interlocked.Exchange(ref hasRun, 1) == 0)
        YourCodeToRun();
}

评论

0赞 David Thielen 11/11/2023
我想过使用计数,但有些场景可以调用两次。所以我需要有两个不同的布尔值(或整数)。OnInitializedAsync()
0赞 Guru Stron 11/11/2023
@DavidThielen您可以使用。Interlocked.Exchange()
0赞 David Thielen 11/11/2023
我看了一下,但我没有看到任何方法可以仅用 2 个变量来解决所有这些问题。它是如此接近,我想也许有办法。但这就是我发帖的原因,因为我无法弄清楚。使这如此血腥的困难是我不能在 OnInitializedAsync/OnAfterRenderAsync 中使用计数器。我必须单独跟踪每个完成情况。Interlocked.Exchange()
2赞 Theodor Zoulias 11/11/2023
@DavidThielen我会不太关心性能,而更担心比赛条件。多个单独的原子操作不等于一个组合的原子操作。如果两个线程以交错方式进行调用,可能会发生有趣的事情。我并不是说这个答案中肯定存在竞争条件。我是说,证明没有并非易事。Interlocked
1赞 David Thielen 11/11/2023
@TheodorZoulias我同意你的看法。这是该方法的一个论据,因为这是一个单一的原子操作。谢谢 - 非常好的观点。lock
1赞 David Thielen 11/13/2023 #3

首先,我建议阅读其他答案和评论。这有很多微妙之处,讨论贯穿了所有这些。非常值得花时间。

其次,对于我的特定用例,锁不是必需的。虽然 和 ,但它们都在同一个 UI 线程中执行。因此,它们不能同时调用这些方法。OnInitializeAsync()OnAfterRenderAsync()Try

第三,如果你确实需要这个功能,其中调用可以来自不同的线程,请使用我的方法。使用的其他方法进行安全的单独调用。但是连续 2 或 3 次调用不是单个原子操作。它们是 2 或 3 个离散的原子动作。对于此用例,您需要将所有这些操作作为单个原子操作。lockInterlockedInterlocked