KeyUp 事件处理程序中的信号量——锁定键盘输入

Semaphore in KeyUp event handler -- locks keyboard input

我正在尝试了解 Semaphore 的。 简而言之,我在 KeyUp event handler 中放置了 "long" 运行 过程(访问网络资源)InitializeNamesAsync("","","")。当 viewNamesInitializeNamesAsync() 初始化时,我试图让用户在不减速的情况下连续打字。由于用户将连续键入,因此 KeyUp event handler 将被调用多次,而 InitializeNamesAsync() 方法是 运行.

虽然下面的代码可以正常编译,但它会永远锁定,完全停止键盘输入。

所以我的问题是:

  1. 这是对 Semaphore 的适当使用吗?
  2. 我怎样才能完成这项工作?
  3. 有没有更好的方法?

TIA

已定义

ResourceLock = new Semaphore(0, 1);


private async void _cboLastName_KeyUpAsync(object sender, KeyEventArgs e)
    {
        if (viewNames == null)
        {

            ResourceLock.WaitOne();
            await InitializeNamesAsync("", "", "");
            ResourceLock.Release();

        }
   }

你的设计存在根本问题,虽然你使用 Semaphore 允许多个线程进入并执行临界区内的事件,但挑战是,你是哪个线程阻塞?

由于事件是在 Ui thread 上执行的,它只有 1 并且是唯一的,所以发生的事情是:

  • 您的代码进入事件,在 Ui 线程上为信号量调用 WaitOne 并且它完成了您被阻止,它甚至没有按预期执行异步方法

Check the out the following Console code, what do you think is the result ?

以下代码导致死锁,因为 Ui 或主控制台线程正在等待自身

async Task Main()
{

    Semaphore s = new Semaphore(0, 2);
    for(int x = 0; x < 5;x++)
    {
        s.WaitOne();
        await Test(x);      
        s.Release();
    }    
}

async Task Test(int x)
{
    $"Entering : {x}".Dump();
    await Task.Delay(3000);
}
  • 在上面的代码中,await Test(x);s.Release(); 从未被调用

What are the options, review modified design:

async Task Main()
{   
    for(int x = 0; x < 5;x++)
    {
        await Test(x);
        s.WaitOne();
    }   
}

Semaphore s = new Semaphore(0,2);

async Task Test(int x)
{
    $"Entering : {x}".Dump();
    await Task.Delay(3000);
    s.Release();
}

What's different here:

  1. 在调用信号量 WaitOne 之前调用了异步方法
  2. 信号量 Release 发生 post 异步方法的完成,而不是在同一线程上(在本例中是在线程池线程上)

你会发现这段代码会成功执行,没有任何死锁

What's the solution:

  1. 不要在像 Ui 线程这样的唯一线程上调用 WaitOne,这会导致死锁,尤其是当 Release 也被安排在同一线程上时
  2. 在单独的线程上调用Release(我使用了Async方法,在这种情况下使用Threadpool线程)

Other Details:

  • 理想情况下,Semaphore 是为多个线程进入临界区而设计的,如果您只期望一个线程,那么 Sempahore 可能不是正确的选择,但它有助于向线程发送信号,这与锁不同,您也可以查看 ManualResetEventAutoResetEvent,支持 Signaling / EventWaitHandle 个用例

线程被阻塞,因为你输入了两次,而信号量不允许输入同一个线程两次(而例如 Monitor.Enter 允许——但不清楚为什么你在这里需要它)。

据我所知,您需要在后台启动初始化。

由于它是 UI 线程,您可能不需要使用同步原语(至少在这种情况下,通常不需要)。我认为只要有两个变量就足够了

正在初始化

并使用类似

的代码进行初始化
private async void EnsureInitialized()
{

    if(!initialized && !beingInitialized)
   {
    beingInitalized = true;
    await StartInitialization();
    initalized = true;
    beingInitialized = false;
   }
}

然后把它称为火而忘记

喜欢

private async void _cboLastName_KeyUpAsync(object sender, KeyEventArgs e)
{
    EnsureInitialized();
    ...