Anti-padrão E/S síncronaSynchronous I/O antipattern

Bloquear o thread que realiza a chamada enquanto a E/S é concluída pode reduzir o desempenho e afetar a escalabilidade vertical.Blocking the calling thread while I/O completes can reduce performance and affect vertical scalability.

Descrição do problemaProblem description

Uma operação de E/S síncrona bloqueia o thread de chamada enquanto a E/S é concluída.A synchronous I/O operation blocks the calling thread while the I/O completes. O thread de chamada entra num estado de espera e não pode realizar trabalho útil durante este intervalo, perdendo recursos de processamento.The calling thread enters a wait state and is unable to perform useful work during this interval, wasting processing resources.

Exemplos comuns de E/S são:Common examples of I/O include:

  • Obter ou manter dados numa base de dados ou qualquer tipo de armazenamento persistente.Retrieving or persisting data to a database or any type of persistent storage.
  • Enviar um pedido para um serviço Web.Sending a request to a web service.
  • Publicar uma mensagem ou obter uma mensagem de uma fila.Posting a message or retrieving a message from a queue.
  • Escrever ou ler a partir de um ficheiro local.Writing to or reading from a local file.

Este anti-padrão ocorre normalmente porque:This antipattern typically occurs because:

  • Parece ser a forma mais intuitiva para realizar uma operação.It appears to be the most intuitive way to perform an operation.
  • A aplicação precisa de uma resposta de um pedido.The application requires a response from a request.
  • A aplicação utiliza uma biblioteca que oferece apenas métodos síncronos para E/S.The application uses a library that only provides synchronous methods for I/O.
  • Uma biblioteca externa realiza operações de E/S síncronas internamente.An external library performs synchronous I/O operations internally. Uma única chamada de E/S síncrona pode bloquear uma cadeia de chamadas completa.A single synchronous I/O call can block an entire call chain.

O seguinte código carrega um ficheiro para o armazenamento de blobs do Azure.The following code uploads a file to Azure blob storage. Existem dois locais onde o código bloqueia enquanto aguarda pela E/S síncrona, o método CreateIfNotExists e o método UploadFromStream.There are two places where the code blocks waiting for synchronous I/O, the CreateIfNotExists method and the UploadFromStream method.

var blobClient = storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("uploadedfiles");

container.CreateIfNotExists();
var blockBlob = container.GetBlockBlobReference("myblob");

// Create or overwrite the "myblob" blob with contents from a local file.
using (var fileStream = File.OpenRead(HostingEnvironment.MapPath("~/FileToUpload.txt")))
{
    blockBlob.UploadFromStream(fileStream);
}

Eis um exemplo de espera de uma resposta de um serviço externo.Here's an example of waiting for a response from an external service. O método GetUserProfile chama um serviço remoto que devolve um UserProfile.The GetUserProfile method calls a remote service that returns a UserProfile.

public interface IUserProfileService
{
    UserProfile GetUserProfile();
}

public class SyncController : ApiController
{
    private readonly IUserProfileService _userProfileService;

    public SyncController()
    {
        _userProfileService = new FakeUserProfileService();
    }

    // This is a synchronous method that calls the synchronous GetUserProfile method.
    public UserProfile GetUserProfile()
    {
        return _userProfileService.GetUserProfile();
    }
}

Pode encontrar o código completo para ambos estes exemplos aqui.You can find the complete code for both of these examples here.

Como resolver o problemaHow to fix the problem

Substitua operações de E/S síncronas por operações assíncronas.Replace synchronous I/O operations with asynchronous operations. Esta ação liberta o thread atual para continuar a realizar trabalho significativo, em vez de bloquear, e ajuda a melhorar a utilização de recursos de computação.This frees the current thread to continue performing meaningful work rather than blocking, and helps improve the utilization of compute resources. Executar a E/S de modo assíncrono é particularmente eficaz para processar um aumento inesperado de pedidos de aplicações de cliente.Performing I/O asynchronously is particularly efficient for handling an unexpected surge in requests from client applications.

Muitas bibliotecas oferecem versões de operações síncronas e assíncronas dos métodos.Many libraries provide both synchronous and asynchronous versions of methods. Sempre que puder, utilize as versões assíncronas.Whenever possible, use the asynchronous versions. Segue a versão assíncrona do exemplo anterior que carrega um ficheiro para o armazenamento de blobs do Azure.Here is the asynchronous version of the previous example that uploads a file to Azure blob storage.

var blobClient = storageAccount.CreateCloudBlobClient();
var container = blobClient.GetContainerReference("uploadedfiles");

await container.CreateIfNotExistsAsync();

var blockBlob = container.GetBlockBlobReference("myblob");

// Create or overwrite the "myblob" blob with contents from a local file.
using (var fileStream = File.OpenRead(HostingEnvironment.MapPath("~/FileToUpload.txt")))
{
    await blockBlob.UploadFromStreamAsync(fileStream);
}

O operador await devolve controlo ao ambiente da chamada enquanto a operação assíncrona é realizada.The await operator returns control to the calling environment while the asynchronous operation is performed. Depois desta instrução, o código atua como uma continuação que é executada quando a operação assíncrona é concluída.The code after this statement acts as a continuation that runs when the asynchronous operation has completed.

Um serviço bem estruturado também deve disponibilizar operações assíncronas.A well designed service should also provide asynchronous operations. Eis uma versão assíncrona do serviço Web que devolve perfis de utilizador.Here is an asynchronous version of the web service that returns user profiles. O método GetUserProfileAsync depende de ter uma versão assíncrona do serviço de Perfil de Utilizador.The GetUserProfileAsync method depends on having an asynchronous version of the User Profile service.

public interface IUserProfileService
{
    Task<UserProfile> GetUserProfileAsync();
}

public class AsyncController : ApiController
{
    private readonly IUserProfileService _userProfileService;

    public AsyncController()
    {
        _userProfileService = new FakeUserProfileService();
    }

    // This is an synchronous method that calls the Task based GetUserProfileAsync method.
    public Task<UserProfile> GetUserProfileAsync()
    {
        return _userProfileService.GetUserProfileAsync();
    }
}

Para bibliotecas que não disponibilizam versões assíncronas de operações, poderá criar wrappers assíncronos em torno de métodos síncronos selecionados.For libraries that don't provide asynchronous versions of operations, it may be possible to create asynchronous wrappers around selected synchronous methods. Siga esta abordagem com cuidado.Follow this approach with caution. Apesar de poder melhorar a capacidade de resposta no thread que invoca o wrapper assíncrono, na realidade consome mais recursos.While it may improve responsiveness on the thread that invokes the asynchronous wrapper, it actually consumes more resources. Pode ser criado um thread adicional e existe overhead associado ao sincronizar o trabalho feito por este thread.An extra thread may be created, and there is overhead associated with synchronizing the work done by this thread. São apresentadas algumas desvantagens nesta mensagem de blogue: Devo expor wrappers assíncronos para métodos síncronos?Some tradeoffs are discussed in this blog post: Should I expose asynchronous wrappers for synchronous methods?

Eis um exemplo de um wrapper assíncrono em torno de um método síncrono.Here is an example of an asynchronous wrapper around a synchronous method.

// Asynchronous wrapper around synchronous library method
private async Task<int> LibraryIOOperationAsync()
{
    return await Task.Run(() => LibraryIOOperation());
}

Agora, o código de chamada pode aguardar no wrapper:Now the calling code can await on the wrapper:

// Invoke the asynchronous wrapper using a task
await LibraryIOOperationAsync();

ConsideraçõesConsiderations

  • As operações de E/S em que se espera que tenham uma vida curta e dificilmente causarão disputa, poderão ter um melhor desempenho enquanto operações síncronas.I/O operations that are expected to be very short lived and are unlikely to cause contention might be more performant as synchronous operations. Um exemplo poderá ser a leitura de pequenos ficheiros numa unidade SSD.An example might be reading small files on an SSD drive. O overhead da emissão de uma tarefa para outro thread e a sincronização com esse thread quando a tarefa está concluída, pode superar os benefícios de E/S assíncronas.The overhead of dispatching a task to another thread, and synchronizing with that thread when the task completes, might outweigh the benefits of asynchronous I/O. No entanto, estes casos são relativamente raros e a maioria das operações de E/S devem ser feitas de forma assíncrona.However, these cases are relatively rare, and most I/O operations should be done asynchronously.

  • Melhorar o desempenho da E/S pode fazer com que outras partes do sistema fiquem estranguladas.Improving I/O performance may cause other parts of the system to become bottlenecks. Por exemplo, desbloquear threads poderá resultar num volume maior de pedidos simultâneos para recursos partilhados, levando, por sua vez, à carência ou limitação de recursos.For example, unblocking threads might result in a higher volume of concurrent requests to shared resources, leading in turn to resource starvation or throttling. Se isso se tornar um problema, poderá ter de aumentar horizontalmente o número de servidores Web ou dividir arquivos de dados para reduzir a contenção.If that becomes a problem, you might need to scale out the number of web servers or partition data stores to reduce contention.

Como detetar o problemaHow to detect the problem

Para os utilizadores, a aplicação pode parecer que não responde ou que fica suspensa periodicamente.For users, the application may seem unresponsive or appear to hang periodically. A aplicação poderá falhar com exceções de tempo limite.The application might fail with timeout exceptions. Estas falhas também podem devolver erros de HTTP 500 (Servidor Interno).These failures could also return HTTP 500 (Internal Server) errors. No servidor, os pedidos de cliente recebidos poderão ser bloqueados até que um thread se torne disponível, resultando em comprimentos de fila de pedido excessivos, apresentados como erros de HTTP 503 (Serviço Indisponível).On the server, incoming client requests might be blocked until a thread becomes available, resulting in excessive request queue lengths, manifested as HTTP 503 (Service Unavailable) errors.

Pode realizar os passos seguintes para ajudar a identificar o problema:You can perform the following steps to help identify the problem:

  1. Monitorize o sistema de produção e determine se os threads de trabalho bloqueados estão a estrangular o débito.Monitor the production system and determine whether blocked worker threads are constraining throughput.

  2. Se os pedidos estão a ser bloqueados devido à falta de threads, aceda à aplicação para determinar as operações que poderão estar executar a E/S de forma síncrona.If requests are being blocked due to lack of threads, review the application to determine which operations may be performing I/O synchronously.

  3. Realize o teste de carga controlada de cada operação que está a realizar a E/S síncrona, para saber se essas operações estão a afetar o desempenho do sistema.Perform controlled load testing of each operation that is performing synchronous I/O, to find out whether those operations are affecting system performance.

Diagnóstico de exemploExample diagnosis

As secções seguintes aplicam estes passos para o exemplo de aplicação descrito anteriormente.The following sections apply these steps to the sample application described earlier.

Monitorizar o desempenho do servidor WebMonitor web server performance

Para aplicações Web do Azure e funções da Web, é importante monitorizar o desempenho do servidor Web do IIS.For Azure web applications and web roles, it's worth monitoring the performance of the IIS web server. Em particular, preste atenção ao comprimento da fila de pedido para estabelecer se os pedidos estão a ser bloqueados enquanto aguardam pelos threads disponíveis durante períodos de grande atividade.In particular, pay attention to the request queue length to establish whether requests are being blocked waiting for available threads during periods of high activity. Pode recolher estas informações ao ativar o diagnóstico do Azure.You can gather this information by enabling Azure diagnostics. Para obter mais informações, consulte:For more information, see:

Instrumente a aplicação para ver de que forma os pedidos são processados assim que são aceites.Instrument the application to see how requests are handled once they have been accepted. Rastrear o fluxo de um pedido pode ajudar a identificar se está a realizar chamadas de execução lenta e a bloquear o thread atual.Tracing the flow of a request can help to identify whether it is performing slow-running calls and blocking the current thread. A criação de perfis de threads também pode destacar pedidos que estão a ser bloqueados.Thread profiling can also highlight requests that are being blocked.

Testar a carga da aplicaçãoLoad test the application

O gráfico seguinte mostra o desempenho do método GetUserProfile síncrono apresentado anteriormente, em diferentes cargas de até 4000 utilizadores em simultâneo.The following graph shows the performance of the synchronous GetUserProfile method shown earlier, under varying loads of up to 4000 concurrent users. A aplicação é uma aplicação ASP.NET em execução numa função da Web do Serviço Cloud do Azure.The application is an ASP.NET application running in an Azure Cloud Service web role.

Gráfico de desempenho para o exemplo de aplicação realizar operações de E/S síncronas

A operação síncrona é hard-coded para suspender durante dois segundos, para simular a E/S síncrona, pelo que o tempo de resposta mínimo é ligeiramente superior a dois segundos.The synchronous operation is hard-coded to sleep for 2 seconds, to simulate synchronous I/O, so the minimum response time is slightly over 2 seconds. Quando a carga atinge aproximadamente 2500 utilizadores em simultâneo, o tempo médio de resposta atinge um patamar, embora o volume de pedidos por segundo continue a aumentar.When the load reaches approximately 2500 concurrent users, the average response time reaches a plateau, although the volume of requests per second continues to increase. Tenha em atenção que o dimensionamento para estas duas medidas é logarítmico.Note that the scale for these two measures is logarithmic. O número de pedidos por segundo duplica entre este ponto e o fim do teste.The number of requests per second doubles between this point and the end of the test.

No isolamento, não está bem explícito a partir deste teste se a E/S síncrona é um problema.In isolation, it's not necessarily clear from this test whether the synchronous I/O is a problem. Numa carga mais pesada, a aplicação pode alcançar um ponto limite onde o servidor Web já não pode processar pedidos atempadamente, fazendo com que aplicações de cliente recebam exceções de tempo limite excedido.Under heavier load, the application may reach a tipping point where the web server can no longer process requests in a timely manner, causing client applications to receive time-out exceptions.

Os pedidos recebidos são colocados em fila pelo servidor Web de IIS e entregues a um thread em execução no conjunto de threads ASP.NET.Incoming requests are queued by the IIS web server and handed to a thread running in the ASP.NET thread pool. Uma vez que cada operação executa a E/S de forma síncrona, o thread está bloqueado até que a operação seja concluída.Because each operation performs I/O synchronously, the thread is blocked until the operation completes. À medida que a carga de trabalho aumenta, eventualmente todos os threads ASP.NET no conjunto de threads são alocados e bloqueados.As the workload increases, eventually all of the ASP.NET threads in the thread pool are allocated and blocked. Nessa altura, quaisquer outros pedidos recebidos têm de aguardar na fila para um thread disponível.At that point, any further incoming requests must wait in the queue for an available thread. À medida que cresce o comprimento da fila, os pedidos começam a alcançar o tempo limite.As the queue length grows, requests start to time out.

Implementar a solução e verificar o resultadoImplement the solution and verify the result

O gráfico seguinte mostra os resultados dos testes de carga da versão assíncrona do código.The next graph shows the results from load testing the asynchronous version of the code.

Gráfico de desempenho para o exemplo de aplicação realizar operações de E/S assíncronas

O débito é muito superior.Throughput is far higher. Na mesma duração que o teste anterior, o sistema lida com sucesso a um aumento quase dez vezes superior no débito, conforme medido nos pedidos por segundo.Over the same duration as the previous test, the system successfully handles a nearly tenfold increase in throughput, as measured in requests per second. Além disso, o tempo médio de resposta é relativamente constante e permanece aproximadamente 25 vezes mais pequeno do que o teste anterior.Moreover, the average response time is relatively constant and remains approximately 25 times smaller than the previous test.