SemaphoreSlim (.NET) 是否阻止同一线程进入块? [英] Does SemaphoreSlim (.NET) prevent same thread from entering block?

查看:16
本文介绍了SemaphoreSlim (.NET) 是否阻止同一线程进入块?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have read the docs for SemaphoreSlim SemaphoreSlim MSDN which indicates that the SemaphoreSlim will limit a section of code to be run by only 1 thread at a time if you configure it as:

SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);

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

However, it doesn't indicate if it stops the same thread from accessing that code. This comes up with async and await. If one uses await in a method, control leaves that method and returns when whatever task or thread has completed. In my example, I use a button with an async button handler. It calls another method (Function1) with 'await'. Function1 in turn calls

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

在我的 Task.Run() 周围,我有一个 SemaphoreSlim.它似乎确实阻止了同一线程进入 Function2.但这并不能从文档中得到保证(正如我所阅读的那样),我想知道是否可以指望这一点.

Around my Task.Run() I have a SemaphoreSlim. It sure seems like it stops the same thread from getting to Function2. But this is not guaranteed (as I read it) from the documentation and I wonder if that can be counted on.

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

I have posted my complete example below.

谢谢,

戴夫

 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 总是在给定计数器再次启动之前完成.

Sample output if you click button 3 times. Notice that Function2 always finishes for a given counter before it starts again.

    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 调用,你会得到:

If you get rid of the SemaphoreSlim calls you'll get:

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

推荐答案

来自 文档:

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

The SemaphoreSlim class doesn’t enforce thread or task identity on calls to the Wait, WaitAsync, and Release methods

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

In other words, the class doesn't look to see which thread is calling it. It's just a simple counter. The same thread can acquire the semaphore multiple times, and that will be the same as if multiple threads acquired the semaphore. If the thread count remaining is down to 0, then even if a thread already was one that had acquired the semaphore that thread, if it calls Wait(), it will block until some other thread releases the semaphore.

因此,对于 async/await,事实上 await 可能会或可能不会在它所在的同一线程中恢复开始无所谓.只要您保持 Wait()Release() 调用的平衡,它就会像人们希望的那样工作.

So, with respect to async/await, the fact that an await may or may not resume in the same thread where it was started doesn't matter. As long as you keep your Wait() and Release() calls balanced, it will work as one would hope and expect.

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

In your example, you're even waiting on the semaphore asynchronously, and thus not blocking any thread. Which is good, because otherwise you'd deadlock the UI thread the second time you pressed your button.


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

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

Note in particular caveats on re-entrant/recursive locking, especially with async/await. Thread synchronization is tricky enough as it is, and that difficulty is what async/await is designed to simplify. And it does so significantly in most cases. But not when you mix it with yet another synchronization/locking mechanism.

这篇关于SemaphoreSlim (.NET) 是否阻止同一线程进入块?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆