C# SemáforoSlim

Resumo : neste tutorial, você aprenderá como usar o C# SemaphoreSlimpara limitar o número de threads que podem acessar um recurso compartilhado simultaneamente.

Introdução à classe C# SemaphoreSlim

Um semáforo é um mecanismo para limitar o número de threads que podem acessar um recurso compartilhado simultaneamente.

O conceito de semáforo é baseado em um contador que especifica a quantidade de recursos disponíveis.

Para acessar o recurso compartilhado, um thread precisa solicitar uma permissão do semáforo.

Se a licença estiver disponível, o semáforo diminuirá o contador. Entretanto, se o contador for zero, o semáforo será bloqueado até que uma licença seja disponibilizada.

Depois que o thread concluir o processamento do recurso compartilhado, ele precisará liberar a permissão de volta ao semáforo para que outros threads possam obter a permissão. Quando a thread libera a permissão, o semáforo incrementa o contador

C# possui duas classes que implementam o conceito de semáforo: Semaphoree SemaphoreSlim.

A Semaphoreclasse está disponível desde a versão inicial do .NET Framework. A SemaphoreSlimé a classe mais recente introduzida no .NET Framework 4.0e no .NET core.

A SemaphoreSlimclasse é uma implementação leve da Semaphoreclasse. O SemaphoreSlimé mais rápido e mais eficiente em termos de memória do que a Semaphoreclasse.

Como usar a classe SemaphoreSlim

Para usar a SemaphoreSlimclasse, siga estas etapas:

Primeiro, crie um SemaphoreSlimobjeto e passe o número inicial de permissões para seu construtor:

SemaphoreSlim semaphore = new(3);Linguagem de código:  C#  ( cs )

Neste exemplo, o objeto semáforo possui um contador inicial de 3. Isso significa que até três threads podem acessar recursos compartilhados simultaneamente.

Segundo, chame o WaitAsync()método do objeto semáforo para solicitar uma permissão:

await semaphore.WaitAsync();Linguagem de código:  C#  ( cs )

O WaitAsync()método retorna um Taskobjeto que aguarda a concessão da licença.

Terceiro, chame o Release()método do objeto semáforo para liberar a permissão assim que você concluir o acesso ao recurso compartilhado:

semaphore.Release();Linguagem de código:  C#  ( cs )

É uma boa prática usar o try...finallybloco para garantir que o Release()método seja sempre chamado mesmo se uma exceção for levantada ao acessar o recurso compartilhado:

await semaphore.WaitAsync();

try
{
    // Access the shared resource
}
finally
{
    semaphore.Release();
}Linguagem de código:  C#  ( cs )

O exemplo a seguir demonstra como usar a SemaphoreSlimclasse para permitir que um número limitado de tarefas possa acessar o recurso compartilhado simultaneamente:

using static System.Console; 

SemaphoreSlim semaphore = new(3);
int amount = 0;

async Task AccessAsync(int id)
{
    WriteLine($"Task {id} is waiting to access the amount.");
    await semaphore.WaitAsync();

    try
    {
        WriteLine($"Task {id} is now accessing the amount.");


        // simulate some work
        await Task.Delay(TimeSpan.FromSeconds(1));
        
        // increase the counter
        Interlocked.Increment(ref amount);


        // completed the work
        WriteLine($"Task {id} has completed accessing the amount {amount}");
    }
    finally
    {
        semaphore.Release();
    }
}


// start 10 tasks to access the amount concurrently
var  tasks = new List<Task>();

for (int i = 1; i <= 10; i++)
{
    tasks.Add(AccessAsync(i));
}

// wait for all task to complete
await Task.WhenAll(tasks);

WriteLine("All tasks completed.");
ReadLine();Linguagem de código:  C#  ( cs )

Como funciona.

Primeiro, crie um SemaphoreSlimobjeto que permita que até três tarefas acessem o recurso compartilhado ao mesmo tempo:

SemaphoreSlim semaphore = new(3);Linguagem de código:  C#  ( cs )

A seguir, declare a amountvariável que atua como um recurso compartilhado:

int amount = 0;Linguagem de código:  C#  ( cs )

Em seguida, defina um método assíncrono AccessAsync que receba um parâmetro inteiro id:

async Task AccessAsync(int id)
{
    WriteLine($"Task {id} is waiting to access the amount.");
    await semaphore.WaitAsync();

    try
    {
        WriteLine($"Task {id} is now accessing the amount.");


        // simulate some work
        await Task.Delay(TimeSpan.FromSeconds(1));
        
        // increase the counter
        Interlocked.Increment(ref amount);


        // completed the work
        WriteLine($"Task {id} has completed accessing the amount {amount}");
    }
    finally
    {
        semaphore.Release();
    }
}Linguagem de código:  C#  ( cs )

O AccessAsync()método simula o acesso à variável compartilhada amountusando o objeto semáforo.

A AccessAsync()chamada do método WaitAsync()do objeto semáforo para aguardar uma licença disponível, incrementa a amountvariável usando o Increment()método da Interlockedclasse e retorna a licença de volta ao semáforo usando o Release()método do objeto semáforo.

O AccessAsync()método também grava algumas mensagens no console para indicar o início e o fim da tarefa.

Depois disso, crie dez tarefas que executam o AccessAsyncmétodo e aguarde a conclusão delas usando o Task.WhenAll()método:

// start 10 tasks to access the amount concurrently
var  tasks = new List<Task>();

for (int i = 1; i <= 10; i++)
{
    tasks.Add(AccessAsync(i));
}

// wait for all task to complete
await Task.WhenAll(tasks);Linguagem de código:  C#  ( cs )

Por fim, escreva uma mensagem no console para notificar a conclusão de todas as tarefas e aguarde o usuário pressionar uma tecla antes de encerrar o programa:

WriteLine("All tasks completed.");
ReadLine();Linguagem de código:  C#  ( cs )

O seguinte mostra a saída do programa:

Task 1 is waiting to access the amount.
Task 1 is now accessing the amount.
Task 2 is waiting to access the amount.
Task 2 is now accessing the amount.
Task 3 is waiting to access the amount.
Task 3 is now accessing the amount.
Task 4 is waiting to access the amount.
Task 5 is waiting to access the amount.
Task 6 is waiting to access the amount.
Task 7 is waiting to access the amount.
Task 8 is waiting to access the amount.
Task 9 is waiting to access the amount.
Task 10 is waiting to access the amount.
Task 1 has completed accessing the amount 1
Task 3 has completed accessing the amount 2
Task 5 is now accessing the amount.
Task 4 is now accessing the amount.
Task 2 has completed accessing the amount 3
Task 6 is now accessing the amount.
Task 5 has completed accessing the amount 6
Task 6 has completed accessing the amount 6
Task 8 is now accessing the amount.
Task 7 is now accessing the amount.
Task 4 has completed accessing the amount 6
Task 9 is now accessing the amount.
Task 9 has completed accessing the amount 7
Task 8 has completed accessing the amount 8
Task 7 has completed accessing the amount 9
Task 10 is now accessing the amount.
Task 10 has completed accessing the amount 10
All tasks completed.Linguagem de código:  C#  ( cs )

A saída mostra que:

  • Apenas três tarefas podem acessar o valor da variável compartilhada simultaneamente.
  • Assim que uma licença estiver disponível, a próxima tarefa poderá acessar a variável compartilhada.

Um exemplo prático da classe SemaphoreSlim

O programa a seguir baixa arquivos de forma assíncrona usando classes SemaphoreSlime HttpClient:

using static System.Console;


var client = new HttpClient();
var semaphore = new SemaphoreSlim(3);

async Task DownloadFileAsync(string url)
{
    await semaphore.WaitAsync();

    try
    {
        WriteLine($"Downloading file {url}...");

        var response = await client.GetAsync(url);

        response.EnsureSuccessStatusCode();

        using (var stream = await response.Content.ReadAsStreamAsync())
        using (var fileStream = File.Create(Path.GetFileName(url)))
        {
            await stream.CopyToAsync(fileStream);
        }

        WriteLine($"Completed Downloading file {url}.");
    }
    finally
    {
        semaphore.Release();
    }
}

var urls = new List<string>
{
    "https://www.ietf.org/rfc/rfc791.txt",
    "https://www.ietf.org/rfc/rfc792.txt",
    "https://www.ietf.org/rfc/rfc793.txt",
    "https://www.ietf.org/rfc/rfc794.txt",
    "https://www.ietf.org/rfc/rfc795.txt",
    "https://www.ietf.org/rfc/rfc796.txt",
    "https://www.ietf.org/rfc/rfc797.txt",
    "https://www.ietf.org/rfc/rfc798.txt",
    "https://www.ietf.org/rfc/rfc799.txt",
    "https://www.ietf.org/rfc/rfc800.txt",
};

var tasks = new List<Task>();

foreach (var url in urls)
{
    tasks.Add(DownloadFileAsync(url));
}

await Task.WhenAll(tasks);

WriteLine("Completed downloading all files.");
Linguagem de código:  C#  ( cs )

Como funciona.

Primeiro, crie uma nova instância da HttpClientclasse que faz solicitações HTTP:

var client = new HttpClient();
Linguagem de código:  C#  ( cs )

Segundo, crie um novo SemaphoreSlimobjeto que limite o número de downloads simultâneos a três:

var semaphore = new SemaphoreSlim(3);
Linguagem de código:  C#  ( cs )

Terceiro, defina o DownloadFileAsync()método que usa uma URL como parâmetro. O método espera no semáforo usando o WaitAsync()método, usa o HttpClientobjeto para baixar o arquivo especificado pela URL e salva o arquivo no sistema de arquivos. Além disso, o método libera o semáforo usando o Release()método:

async Task DownloadFileAsync(string url)
{
    await semaphore.WaitAsync();

    try
    {
        WriteLine($"Downloading file {url}...");

        var response = await client.GetAsync(url);

        response.EnsureSuccessStatusCode();

        using (var stream = await response.Content.ReadAsStreamAsync())
        using (var fileStream = File.Create(Path.GetFileName(url)))
        {
            await stream.CopyToAsync(fileStream);
        }

        WriteLine($"Completed Downloading file {url}.");
    }
    finally
    {
        semaphore.Release();
    }
}
Linguagem de código:  JavaScript  ( javascript )

Quarto, defina uma lista de URLs dos arquivos para download:

var urls = new List<string>
{
    "https://www.ietf.org/rfc/rfc791.txt",
    "https://www.ietf.org/rfc/rfc792.txt",
    "https://www.ietf.org/rfc/rfc793.txt",
    "https://www.ietf.org/rfc/rfc794.txt",
    "https://www.ietf.org/rfc/rfc795.txt",
    "https://www.ietf.org/rfc/rfc796.txt",
    "https://www.ietf.org/rfc/rfc797.txt",
    "https://www.ietf.org/rfc/rfc798.txt",
    "https://www.ietf.org/rfc/rfc799.txt",
    "https://www.ietf.org/rfc/rfc800.txt",
};Linguagem de código:  C#  ( cs )

Quinto, crie uma lista de tarefas que executam DownloadFileAsync()simultaneamente e aguarde a conclusão de todas as tarefas usando o Task.WhenAll()método:

var tasks = new List<Task>();

foreach (var url in urls)
{
    tasks.Add(DownloadFileAsync(url));
}

await Task.WhenAll(tasks);Linguagem de código:  C#  ( cs )

Por fim, envie uma mensagem para o console para notificar que o processo de download foi concluído:

WriteLine("Completed downloading all files.");Linguagem de código:  C#  ( cs )

A saída mostra que existem threads que baixam os arquivos simultaneamente:

Downloading file https://www.ietf.org/rfc/rfc791.txt...
Downloading file https://www.ietf.org/rfc/rfc792.txt...
Downloading file https://www.ietf.org/rfc/rfc793.txt...
Completed Downloading file https://www.ietf.org/rfc/rfc793.txt.
Downloading file https://www.ietf.org/rfc/rfc794.txt...
Completed Downloading file https://www.ietf.org/rfc/rfc794.txt.
Downloading file https://www.ietf.org/rfc/rfc795.txt...
Completed Downloading file https://www.ietf.org/rfc/rfc792.txt.
Downloading file https://www.ietf.org/rfc/rfc796.txt...
Completed Downloading file https://www.ietf.org/rfc/rfc791.txt.
Downloading file https://www.ietf.org/rfc/rfc797.txt...
Completed Downloading file https://www.ietf.org/rfc/rfc795.txt.
Downloading file https://www.ietf.org/rfc/rfc798.txt...
Completed Downloading file https://www.ietf.org/rfc/rfc796.txt.
Downloading file https://www.ietf.org/rfc/rfc799.txt...
Completed Downloading file https://www.ietf.org/rfc/rfc797.txt.
Downloading file https://www.ietf.org/rfc/rfc800.txt...
Completed Downloading file https://www.ietf.org/rfc/rfc799.txt.
Completed Downloading file https://www.ietf.org/rfc/rfc800.txt.
Completed Downloading file https://www.ietf.org/rfc/rfc798.txt.
Completed downloading all files.Linguagem de código:  texto simples  ( texto simples )

Resumo

  • O semáforo limita o número de threads que podem acessar recursos compartilhados simultaneamente.
  • Use a SemaphoreSlimclasse C# para implementar o padrão de semáforo.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *