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

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

问题描述

我已经阅读了SemaphoreSlim的文档

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/awaitawait可能会或可能不会在启动它的同一线程中恢复的事实无关紧要.只要您保持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.


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


Related reading:
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旨在简化这一难题.在大多数情况下,它的作用如此明显.但是,当您将其与其他同步/锁定机制混合使用时,则不会.

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天全站免登陆