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

时间:2019-06-19 04:59:56

标签: c# semaphore

我正在尝试了解信号量的。 简而言之,我在InitializeNamesAsync("","","")中放置了一个{long}运行过程(用于访问网络资源)KeyUp event handler。我试图在viewNames初始化InitializeNamesAsync()的同时允许用户连续输入而不会降低速度。由于用户将不断输入内容,因此KeyUp event handler方法运行时,InitializeNamesAsync()将被调用多次。

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

所以我的问题是:

  1. 这是信号量的适当使用吗?
  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();

        }
   }

2 个答案:

答案 0 :(得分:2)

尽管您正在使用Semaphore来允许多个线程进入并执行关键区域内的事件,但是您的设计仍存在基本问题,但挑战在于,您正在阻止哪个线程?

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

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

检查以下控制台代码,您认为结果如何?

以下代码导致死锁,因为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();从未被调用
  

有哪些选项,请查看修改后的设计:

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();
}
  

这里有什么不同:

  1. 在调用信号量WaitOne之前已调用异步方法
  2. 信号量Release发生在异步方法完成后,而不是在同一线程(在这种情况下,在线程池线程上)

您会发现此代码将成功执行且没有任何死锁

  

有什么解决方案:

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

其他详细信息:

  • 理想情况下,信号量用于让多个线程进入关键区域,如果您只期望一个线程,那么Sempahore可能不是正确的选择,但是它可以帮助发信号通知线程(不同于锁),您还可以查看{{1} }和ManualResetEvent,它们支持AutoResetEvent用例

答案 1 :(得分:1)

该线程被阻止是因为您输入了两次并且信号量不允许两次输入相同的线程(例如Monitor.Enter允许-但不清楚为什么在这里需要它)。

据我了解,您需要在后台启动初始化。

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

beingInitialized

并使用类似的代码进行初始化

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();
    ...