【问题标题】:How does SemaphoreSlim.WaitAsync allow for sequential executions of an async function?SemaphoreSlim.WaitAsync 如何允许异步函数的顺序执行?
【发布时间】:2019-03-30 09:57:19
【问题描述】:

这个问题的上下文是一个 WPF 应用程序。 WPF 应用程序使用 DispatcherSynchronizationContext。

如果我的应用程序中有一个调用 Button_Click 处理程序方法的按钮,并且我想确保该函数中的所有代码仅由一个线程执行,我会将其包装在一个信号量中,如图所示?但我不明白这是如何工作的。

假设按钮被点击,我们会点击 WaitAsync() ,它返回一个在输入信号量时完成的任务,所以我猜是马上?然后我们会点击 await GetLengthAsync() ,它会将我们弹回 wpf 消息循环。假设 10 秒过去了,按钮再次被点击,那么我们将再次进入 Button_Click 方法并点击 WaitAsync(),它返回一个在我们输入信号量时完成的任务,并且我们无法输入信号量所以我们弹回消息循环?是这样的吗?

主要问题 - 两次我们点击 WaitAsync() 我们都在同一个线程上,并且我们的信号量限制了并发性,只允许一个线程一次执行该代码块,但它也不允许我们的同一个线程输入该代码?信号量显然不能通过线程4或线程5等其他线程获得,但即使我们的同一个线程再次获得它也无法获得?任何澄清将不胜感激。

private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1,1);

public async void Button_Click(object sender, EventArgs args)
{
    await semaphoreSlim.WaitAsync();

    try
    {
        // GetLengthAsync takes 40 seconds to complete
        int length = await GetLengthAsync();

        // LongComputeFunc takes 30 seconds to complete
        int aggregate = LongComputeFunc(length);
    }
    finally
    {
        semaphoreSlim.Release();
    }
}

【问题讨论】:

  • 这不会很好地结束,重入错误很讨厌。您需要将按钮的 IsEnabled 属性设置为 false,而不是信号量。
  • @HansPassant 这不是异步代码中的常见问题或所需功能吗?还是表示设计错误?
  • 重新进入从来都不是一个理想的能力。您的代码要么是为并发而设计的,要么不是。重入是错误的标志。我第二个@HansPassant,如果你在等待 GetLengthAsync 之前禁用按钮,你会更好。
  • @Nick 我明白了,但它是否与按钮绑定并不是我最关心的问题。我只是想了解一下程序的执行流程,你熟悉它的工作原理吗?
  • 流程清晰。如果您在 LongComputeFunc 运行时单击该按钮,您的代码将从线程池中获取一个线程来阻塞信号量并等待它发出信号,然后再继续执行该方法。但是,如果您单击太多次,您可能会耗尽线程池,即导致其中的所有线程在信号量上等待,然后单击只会排队等待线程池。当 LongComputeFunc 完成时,如果同步上下文在主线程上运行 Release,你会很幸运。否则你可能会死锁。

标签: c# multithreading asynchronous semaphore


【解决方案1】:

假设按钮被点击,我们会点击 WaitAsync() ,它返回一个在输入信号量时完成的任务,所以我猜是马上?然后我们会点击 await GetLengthAsync() ,它会将我们弹回 wpf 消息循环。

是的,是的。

假设10秒过去了,按钮又被点击了,那么我们再次进入Button_Click方法,点击WaitAsync(),返回一个任务,当我们进入信号量时完成,我们不能进入信号量所以我们弹跳回到消息循环?是这样的吗?

是的。

主要问题 - 我们两次点击 WaitAsync() 时,我们都在同一个线程上,我们的信号量限制了并发性,只允许一个线程一次执行该代码块,但它不允许我们的同一个线程进入该代码块代码呢?信号量显然不能通过thread4或thread5之类的其他线程获得,但即使我们的同一个线程再次获得它也无法获得?

没错。一些同步协调原语确实具有允许递归锁的能力(例如,Monitor),但其他的则没有(例如,Mutex)。但是,异步协调原语支持递归锁是不自然的。 (就我个人而言,我是against recursive locking in general)。同步协调原语可以摆脱递归,因为存在“拥有”锁的“线程”的概念。对于异步协调原语,没有 线程 拥有锁的概念。相反,“代码块”拥有锁。

所以,说SemaphoreSlim.WaitAsync 不是递归的(也不应该是递归的),这是一种冗长的说法。

现在,这是否是一个好的用户体验设计是一个不同的问题。如 cmets 中所述,如果您只希望一次启动一个按钮,则 UI 更常见的是禁用一个代表长时间运行的操作的按钮。也就是说,可以使用SemaphoreSlim 方法,如果您想要允许用户排队多个长时间运行的操作。在这种情况下,SemaphoreSlim 就像您的代码的一种隐式队列。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-16
    • 2019-12-07
    • 1970-01-01
    • 2020-07-08
    • 1970-01-01
    • 2019-12-22
    • 1970-01-01
    相关资源
    最近更新 更多