【问题标题】:Does SemaphoreSlim (.NET) prevent same thread from entering block?SemaphoreSlim (.NET) 是否阻止同一线程进入块?
【发布时间】:2017-04-20 11:51:36
【问题描述】:

我已阅读 SemaphoreSlim SemaphoreSlim MSDN 的文档 这表明 SemaphoreSlim 将限制一段代码一次只能由 1 个线程运行,如果您将其配置为:

SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);

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

await Task.Run(() => Function2(beginCounter));

在我的 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;
    }
}
}

单击按钮 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

如果你摆脱 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

【问题讨论】:

    标签: c# .net async-await semaphore reentrancy


    【解决方案1】:

    来自the documentation

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

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

    因此,对于async/awaitawait 可能会或可能不会在启动它的同一线程中恢复这一事实并不重要。只要您保持Wait()Release() 呼叫平衡,它就会像人们希望和期望的那样工作。

    在您的示例中,您甚至在异步等待信号量,因此不会阻塞任何线程。这很好,否则你会在第二次按下按钮时死锁 UI 线程。


    相关阅读:
    Resource locking between iterations of the main thread (Async/Await)
    Why does this code not end in a deadlock
    Locking with nested async calls

    特别注意重入/递归锁定,尤其是async/await。线程同步本身就足够棘手,而 async/await 旨在简化这一困难。在大多数情况下,它的作用非常显着。但当您将它与另一种同步/锁定机制混合使用时则不然。

    【讨论】:

      猜你喜欢
      • 2021-12-05
      • 1970-01-01
      • 2017-05-10
      • 1970-01-01
      • 1970-01-01
      • 2020-11-30
      • 2012-01-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多