SemaphoreSlim(.NET)是否阻止相同的线程进入块?

Dav*_*ave 14 .net c# semaphore reentrancy async-await

我已经阅读了SemaphoreSlim SemaphoreSlim MSDN的文档, 它表明SemaphoreSlim会限制一段代码,如果你将它配置为一次只能由一个线程运行:

SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
Run Code Online (Sandbox Code Playgroud)

但是,它不表示它是否阻止相同的线程访问该代码.这提出了异步和等待.如果在方法中使用await,则控制将离开该方法,并在任何任务或线程完成时返回.在我的示例中,我使用了一个带有异步按钮处理程序的按钮.它使用'await'调用另一个方法(Function1).Function1依次调用

await Task.Run(() => Function2(beginCounter));
Run Code Online (Sandbox Code Playgroud)

在我的Task.Run()周围,我有一个SemaphoreSlim.肯定看起来它会阻止同一个线程进入Function2.但是这并不能保证(正如我读到的那样)来自文档,我想知道是否可以依靠它.

我在下面发布了完整的例子.

谢谢,

戴夫

 using System;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Windows;

 namespace AsynchAwaitExample
 {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);
    public MainWindow()
    {
        InitializeComponent();
    }

    static int beginCounter = 0;
    static int endCounter = 0;
    /// <summary>
    /// Suggest hitting button 3 times in rapid succession
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private async void button_Click(object sender, RoutedEventArgs e)
    {
        beginCounter++;
        endCounter++;
        // Notice that if you click fast, you'll get all the beginCounters first, then the endCounters
        Console.WriteLine("beginCounter: " + beginCounter + " threadId: " + Thread.CurrentThread.ManagedThreadId);
        await Function1(beginCounter);
        Console.WriteLine("endCounter: " + endCounter + " threadId: " + Thread.CurrentThread.ManagedThreadId);
    }

    private async Task Function1(int beginCounter)
    {
        try
        {
            Console.WriteLine("about to grab lock" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter);
            await _semaphoreSlim.WaitAsync();  // get rid of _semaphoreSlim calls and you'll get into beginning of Function2 3 times before exiting
            Console.WriteLine("grabbed lock" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter);
            await Task.Run(() => Function2(beginCounter));
        }
        finally
        {
            Console.WriteLine("about to release lock" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter);
            _semaphoreSlim.Release();
            Console.WriteLine("released lock" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter);
        }

    }

    private void Function2(int beginCounter)
    {
        Console.WriteLine("Function2 start" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter);
        Thread.Sleep(1000);
        Console.WriteLine("Function2 end" + " threadId: " + Thread.CurrentThread.ManagedThreadId + " beginCounter: " + beginCounter);
        return;
    }
}
}
Run Code Online (Sandbox Code Playgroud)

如果单击按钮3次,则输出示例.请注意,在重新启动之前,Function2始终为给定计数器完成.

    beginCounter: 1 threadId: 9
about to grab lock threadId: 9 beginCounter: 1
grabbed lock threadId: 9 beginCounter: 1
Function2 start threadId: 13 beginCounter: 1
beginCounter: 2 threadId: 9
about to grab lock threadId: 9 beginCounter: 2
beginCounter: 3 threadId: 9
about to grab lock threadId: 9 beginCounter: 3
Function2 end threadId: 13 beginCounter: 1
about to release lock threadId: 9 beginCounter: 1
released lock threadId: 9 beginCounter: 1
grabbed lock threadId: 9 beginCounter: 2
Function2 start threadId: 13 beginCounter: 2
endCounter: 3 threadId: 9
Function2 end threadId: 13 beginCounter: 2
about to release lock threadId: 9 beginCounter: 2
released lock threadId: 9 beginCounter: 2
endCounter: 3 threadId: 9
grabbed lock threadId: 9 beginCounter: 3
Function2 start threadId: 13 beginCounter: 3
Function2 end threadId: 13 beginCounter: 3
about to release lock threadId: 9 beginCounter: 3
released lock threadId: 9 beginCounter: 3
endCounter: 3 threadId: 9
Run Code Online (Sandbox Code Playgroud)

如果你摆脱了SemaphoreSlim的电话,你会得到:

beginCounter: 1 threadId: 10
about to grab lock threadId: 10 beginCounter: 1
grabbed lock threadId: 10 beginCounter: 1
Function2 start threadId: 13 beginCounter: 1
beginCounter: 2 threadId: 10
about to grab lock threadId: 10 beginCounter: 2
grabbed lock threadId: 10 beginCounter: 2
Function2 start threadId: 14 beginCounter: 2
beginCounter: 3 threadId: 10
about to grab lock threadId: 10 beginCounter: 3
grabbed lock threadId: 10 beginCounter: 3
Function2 start threadId: 15 beginCounter: 3
Function2 end threadId: 13 beginCounter: 1
about to release lock threadId: 10 beginCounter: 1
released lock threadId: 10 beginCounter: 1
endCounter: 3 threadId: 10
Function2 end threadId: 14 beginCounter: 2
about to release lock threadId: 10 beginCounter: 2
released lock threadId: 10 beginCounter: 2
endCounter: 3 threadId: 10
Run Code Online (Sandbox Code Playgroud)

Pet*_*iho 18

文档:

SemaphoreSlim类在调用Wait,WaitAsync和Release方法时不强制执行线程或任务标识

换句话说,该类不会查看哪个线程正在调用它.这只是一个简单的计数器.同一个线程可以多次获取信号量,这与多个线程获取信号量的情况相同.如果剩余的线程数减少到0,那么即使一个线程已经获得了该线程的信号量,如果它调用了Wait()它,它将阻塞直到某个其他线程释放信号量.

因此,对于async/ await,await可能会或可能不会在它启动的同一个线程中恢复的事实并不重要.只要你保持你的Wait()Release()呼叫平衡,它将按照人们希望和期望的方式工作.

在您的示例中,您甚至异步地等待信号量,因此不会阻塞任何线程.哪个好,因为否则你第二次按下按钮就会使UI线程死锁.


相关阅读:
主线程迭代之间的资源锁定(Async/Await)
为什么这段代码不会以死锁结束
使用嵌套异步调用锁定

请特别注意重入/递归锁定的注意事项,尤其是async/ await.线程同步够棘手的,因为它是,那难度是什么async/ await是旨在简化.在大多数情况下,它确实如此显着.但是当你将它与另一个同步/锁定机制混合时却不是.