Не SemaphoreSlim (.Неть) не допустить потока от входа блока?

Я прочитал документы для SemaphoreSlim SemaphoreSlim MSDN что указывает на то, что SemaphoreSlim ограничит раздел кода для запуска только 1 потоком за раз, если вы настроите его как:

SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1, 1);

однако он не указывает, останавливает ли он то же самое поток от доступа к этому коду. Это происходит с async и await. Если в методе используется await, элемент управления покидает этот метод и возвращается после завершения любой задачи или потока. В в моем примере я использую кнопку с обработчиком кнопки async. Он вызывает другой метод (Function1) с "await". Function1 в свою очередь вызывает

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

вокруг моей задачи.Выполнить() у меня 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

1 ответов


С документация:

класс SemaphoreSlim не применяет идентификатор потока или задачи при вызовах методов Wait, WaitAsync и Release

другими словами, класс не смотрит, какой поток вызывает его. Это простой счетчик. Один и тот же поток может получить семафор несколько раз, и это будет то же самое, как если бы несколько потоков получили семафор. Если оставшееся количество потоков до 0, то даже если поток уже был тем, который приобрел семафор этого потока, если он вызывает Wait(), Он будет блокировать, пока некоторый другой поток освобождает семафор.

Итак, относительно async/await, тот факт, что await может или не может возобновить работу в том же потоке, где он был запущен, не имеет значения. До тех пор, пока вы держите свой Wait() и Release() называет сбалансированным, он будет работать как можно было бы надеяться и ожидать.

в вашем примере вы даже ожидание семафора асинхронно и, таким образом, не блокирует какой-либо поток. Что хорошо, потому что в противном случае вы бы заблокировали поток пользовательского интерфейса при втором нажатии кнопки.


Связанные чтения:
блокировка ресурсов между итерациями основного потока (Async/Await)
почему этот код не заканчивается в тупике
блокировка с вложенными асинхронными вызовами

обратите внимание, в частности, на предостережения re-entrant / рекурсивная блокировка, особенно с async/await. Синхронизация потоков достаточно сложна, и эта трудность заключается в том, что async/await предназначен для упрощения. И это существенно в большинстве случаев. Но не тогда, когда вы смешиваете его с еще одним механизмом синхронизации/блокировки.