Cancelamento
O .NET Framework versão 4 introduz um novo modelo unificado para o cancelamento cooperativo de operações síncrono longa ou assíncrona. Esse modelo se baseia em um objeto leve um token de cancelamento de chamada. O objeto que chama uma operação cancelável, por exemplo, criando um novo thread ou tarefa, passa o token para a operação. A operação por sua vez pode passar cópias do token para outras operações. Em algum momento posterior, o objeto que criou o token pode usá-la para solicitar que a operação de parar o que está fazendo. Apenas o objeto solicitante pode emitir a solicitação de cancelamento e cada ouvinte é responsável por perceba a solicitação e responder a ele no momento oportuno. A ilustração a seguir mostra a relação entre uma origem de token e todas as cópias do seu símbolo.
O novo modelo de cancelamento torna mais fácil criar bibliotecas e aplicativos com reconhecimento de cancelamento e suporta os seguintes recursos:
Cancelamento é cooperativo e não é forçado a escuta. O ouvinte determina como finalizado em resposta a uma solicitação de cancelamento.
Solicitando é diferente de escuta. Um objeto que chama uma operação cancelável pode controlar quando e (se) cancelamento é solicitado.
O objeto solicitante emite a solicitação de cancelamento de todas as cópias do token usando apenas um método de chamada.
Um ouvinte pode ouvir vários tokens simultaneamente associando-os em um token vinculado.
Código do usuário pode observar e responder a solicitações de cancelamento de código da biblioteca e código da biblioteca pode observar e responder a solicitações de cancelamento de código do usuário.
Ouvintes podem ser notificados de solicitações de cancelamento de polling, registro de retorno de chamada ou aguardando identificadores de espera.
Novos tipos de cancelamento
A nova estrutura de cancelamento é implementada como um conjunto de tipos relacionados, que são listados na tabela a seguir.
Digite um nome |
Descrição |
---|---|
Objeto que cria um token de cancelamento e também emite a solicitação de cancelamento de todas as cópias desse token. |
|
Tipo de valor leve passado para um ou mais ouvintes, normalmente como um parâmetro de método. Ouvintes de monitoram o valor de IsCancellationRequested propriedade do token por sondagem, o retorno de chamada ou o identificador de espera. |
|
Novas sobrecargas dessa exceção aceitam um CancellationToken como um parâmetro de entrada. Ouvintes opcionalmente podem lançar essa exceção para verificar a origem do cancelamento e notificar outros que ele respondeu a uma solicitação de cancelamento. |
O novo modelo de cancelamento está integrado a .NET Framework em vários tipos. Os mais importantes são System.Threading.Tasks.Parallel, System.Threading.Tasks.Task, System.Threading.Tasks.Task<TResult> e System.Linq.ParallelEnumerable. Recomendamos que você use esse novo modelo de cancelamento para todos os novo código de biblioteca e o aplicativo.
Exemplo de código
No exemplo a seguir cria o objeto solicitando uma CancellationTokenSource objeto e, em seguida, passa seu Token propriedade para a operação pode ser cancelado. A operação que recebe a solicitação monitora o valor de IsCancellationRequested propriedade do token pela pesquisa. Quando o valor se torna true, o ouvinte pode finalizar em forma que seja apropriada. Neste exemplo, o método simplesmente sai, que é tudo o que é necessário em muitos casos.
Observação
O exemplo usa o QueueUserWorkItem método para demonstrar que a nova estrutura de cancelamento é compatível com APIs de legado.Para obter um exemplo que usa o novo, preferencial System.Threading.Tasks.Task Digite, consulte Como: Cancelar uma tarefa e seus filhos.
Shared Sub CancelWithThreadPoolMiniSnippet()
'Thread 1: The Requestor
' Create the token source.
Dim cts As New CancellationTokenSource()
' Pass the token to the cancelable operation.
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DoSomeWork), cts.Token)
' Request cancellation by setting a flag on the token.
cts.Cancel()
' end block
End Sub
'Thread 2: The Listener
Shared Sub DoSomeWork(ByVal obj As Object)
Dim token As CancellationToken = CType(obj, CancellationToken)
For i As Integer = 0 To 1000000
' Simulating work.
Thread.SpinWait(5000000)
If token.IsCancellationRequested Then
' Perform cleanup if necessary.
'...
' Terminate the operation.
Exit For
End If
Next
End Sub
static void CancelWithThreadPoolMiniSnippet()
{
//Thread 1: The Requestor
// Create the token source.
CancellationTokenSource cts = new CancellationTokenSource();
// Pass the token to the cancelable operation.
ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);
// Request cancellation by setting a flag on the token.
cts.Cancel();
}
//Thread 2: The Listener
static void DoSomeWork(object obj)
{
CancellationToken token = (CancellationToken)obj;
for (int i = 0; i < 100000; i++)
{
// Simulating work.
Thread.SpinWait(5000000);
if (token.IsCancellationRequested)
{
// Perform cleanup if necessary.
//...
// Terminate the operation.
break;
}
}
}
Cancelamento da operação Versus o cancelamento do objeto
A nova estrutura de cancelamento cancelamento refere-se às operações, não os objetos. A solicitação de cancelamento significa que a operação deve parar tão logo após qualquer limpeza necessária é executada. Um token de cancelamento deve consultar um "operação cancelável", No entanto, essa operação pode ser implementada em seu programa. Após a IsCancellationRequested a propriedade do token tiver sido definida true, não poderá ser redefinido para false. Portanto, os tokens de cancelamento não podem ser reutilizados depois que eles foram cancelados.
Se você precisar de um mecanismo de cancelamento de objeto, você pode basear-o mecanismo de cancelamento da operação, conforme mostrado no exemplo a seguir.
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
' User defined Class with its own method for cancellation
Dim obj1 As New MyCancelableObject()
Dim obj2 As New MyCancelableObject()
Dim obj3 As New MyCancelableObject()
' Register the object's cancel method with the token's
' cancellation request.
token.Register(Sub() obj1.Cancel())
token.Register(Sub() obj2.Cancel())
token.Register(Sub() obj3.Cancel())
' Request cancellation on the token.
cts.Cancel()
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
// User defined Class with its own method for cancellation
var obj1 = new MyCancelableObject();
var obj2 = new MyCancelableObject();
var obj3 = new MyCancelableObject();
// Register the object's cancel method with the token's
// cancellation request.
token.Register(() => obj1.Cancel());
token.Register(() => obj2.Cancel());
token.Register(() => obj3.Cancel());
// Request cancellation on the token.
cts.Cancel();
Se um objeto oferece suporte a mais de uma operação de cancelável simultânea, passe um token separado como entrada para cada operação cancelável distinta. Dessa forma, uma operação pode ser cancelada sem afetar os outros.
Ouvir e responder a solicitações de cancelamento
O delegado do usuário, o implementador de uma operação cancelável determina como finalizar a operação em resposta a uma solicitação de cancelamento. Em muitos casos, o representante do usuário pode apenas executar qualquer limpeza necessária e, em seguida, retornar imediatamente.
Entretanto, em casos mais complexos, ele pode ser necessário para o representante do usuário notificar o código da biblioteca que ocorreu o cancelamento. Em tais casos, a maneira correta para finalizar a operação é chamar o delegado ThrowIfCancellationRequested(), que acarretará uma OperationCanceledException ser acionada. Novas sobrecargas dessa exceção na .NET Framework versão 4 levar uma CancellationToken como um argumento. Código da biblioteca pode tratar essa exceção no thread do representante do usuário e examinar o token da exceção para determinar se a exceção indica cooperativo cancelamento, ou outra situação excepcional.
O Task identificadores de classe OperationCanceledException na forma. Para obter mais informações, consulte Cancelamento da tarefa.
Escuta de Polling
Para cálculos de longa esse loop ou recurse, você pode ouvir para uma solicitação de cancelamento pela sondagem de periodicamente o valor de CancellationToken.IsCancellationRequested propriedade. Se o valor for true, em seguida, o método deve limpar e encerrar o mais rápido possível. A freqüência de polling de ideal depende do tipo de aplicativo. É o desenvolvedor para determinar o melhor de freqüência para qualquer determinado programa de pesquisa. Sondagem propriamente dito não afeta o desempenho significativamente. O exemplo a seguir mostra uma forma possível de poll.
Shared Sub NestedLoops(ByVal rect As Rectangle, ByVal token As CancellationToken)
For x As Integer = 0 To rect.columns
For y As Integer = 0 To rect.rows
' Simulating work.
Thread.SpinWait(5000)
Console.Write("0' end block,1' end block ", x, y)
Next
' Assume that we know that the inner loop is very fast.
' Therefore, checking once per row is sufficient.
If token.IsCancellationRequested = True Then
' Cleanup or undo here if necessary...
Console.WriteLine("\r\nCancelling after row 0' end block.", x)
Console.WriteLine("Press any key to exit.")
' then...
Exit For
' ...or, if using Task:
' token.ThrowIfCancellationRequested()
End If
Next
End Sub
static void NestedLoops(Rectangle rect, CancellationToken token)
{
for (int x = 0; x < rect.columns && !token.IsCancellationRequested; x++)
{
for (int y = 0; y < rect.rows; y++)
{
// Simulating work.
Thread.SpinWait(5000);
Console.Write("{0},{1} ", x, y);
}
// Assume that we know that the inner loop is very fast.
// Therefore, checking once per row is sufficient.
if (token.IsCancellationRequested)
{
// Cleanup or undo here if necessary...
Console.WriteLine("\r\nCancelling after row {0}.", x);
Console.WriteLine("Press any key to exit.");
// then...
break;
// ...or, if using Task:
// token.ThrowIfCancellationRequested();
}
}
}
Para um exemplo completo, consulte Como: Escutar solicitações de cancelamento por pesquisa.
Registrando um retorno de chamada de escuta
Algumas operações podem se tornar bloqueadas de tal forma que eles não é possível verificar o valor do token de cancelamento de maneira oportuna. Nesses casos, você pode registrar um método de retorno de chamada que desbloqueia o método quando é recebida uma solicitação de cancelamento.
O Register método retorna um CancellationTokenRegistration objeto que é usado especificamente para essa finalidade. O exemplo a seguir mostra como usar o Register método para cancelar uma solicitação Web assíncrona.
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
Dim wc As New WebClient()
' To request cancellation on the token
' will call CancelAsync on the WebClient.
token.Register(Sub() wc.CancelAsync())
Console.WriteLine("Starting request")
wc.DownloadStringAsync(New Uri("https://www.contoso.com"))
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
WebClient wc = new WebClient();
// To request cancellation on the token
// will call CancelAsync on the WebClient.
token.Register(() => wc.CancelAsync());
Console.WriteLine("Starting request");
wc.DownloadStringAsync(new Uri("https://www.contoso.com"));
O CancellationTokenRegistration objeto gerencia a sincronização de thread e garante que o retorno de chamada irá parar a execução de um ponto preciso na hora.
Para garantir a capacidade de resposta do sistema e evitar deadlocks, as diretrizes a seguir devem ser seguidas ao registrar retornos de chamada:
O método de retorno de chamada deve ser rápido, porque ele é chamado modo síncrono e, portanto, a chamada para Cancel não retorna até o retorno de chamada retorna.
Se você chamar Dispose enquanto o retorno de chamada está em execução, e você mantiver um bloqueio que o retorno de chamada está aguardando, o programa pode deadlock. Depois de Dispose retorna, você pode liberar todos os recursos necessários para o retorno de chamada.
Retornos de chamada não devem executar qualquer segmento manual ou SynchronizationContext o uso em um retorno de chamada. Se um retorno de chamada deve ser executado em um determinado segmento, use o System.Threading.CancellationTokenRegistration construtor, que permite especificar que o syncContext de destino está ativo SynchronizationContext.Current. Execução manual de threading em um retorno de chamada pode causar o bloqueio.
Para um exemplo completo, consulte Como: Registrar retornos de chamada para solicitações de cancelamento.
Ouvindo, usando um identificador de espera
Quando uma operação cancelável pode bloquear enquanto aguarda uma sincronização primitiva, como um System.Threading.ManualResetEvent ou System.Threading.Semaphore, você pode usar o CancellationToken.WaitHandle propriedade para habilitar a operação de esperar o evento e a solicitação de cancelamento. O identificador de espera do token cancelamento será sinalizado em resposta a uma solicitação de cancelamento e o método pode usar o valor de retorno de WaitAny método para determinar se ele era o cancelamento de token que assinalados. A operação pode, em seguida, simplesmente sair ou lançar um OperationCanceledException, conforme apropriado.
' Wait on the event if it is not signaled.
Dim myWaitHandle(2) As WaitHandle
myWaitHandle(0) = mre
myWaitHandle(1) = token.WaitHandle
Dim eventThatSignaledIndex =
WaitHandle.WaitAny(myWaitHandle, _
New TimeSpan(0, 0, 20))
// Wait on the event if it is not signaled.
int eventThatSignaledIndex =
WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
new TimeSpan(0, 0, 20));
Em código novo que se destina a .NET Framework versão 4, System.Threading.ManualResetEventSlim e System.Threading.SemaphoreSlim oferecem suporte a nova estrutura de cancelamento em seus Wait métodos. Você pode passar o CancellationToken para o método, e quando o cancelamento é solicitado, o evento acorda e lança um OperationCanceledException.
Try
' mres is a ManualResetEventSlim
mres.Wait(token)
Catch e As OperationCanceledException
' Throw immediately to be responsive. The
' alternative is to do one more item of work,
' and throw on next iteration, because
' IsCancellationRequested will be true.
Console.WriteLine("Canceled while waiting.")
Throw
End Try
' Simulating work.
Console.Write("Working...")
Thread.SpinWait(500000)
try
{
// mres is a ManualResetEventSlim
mres.Wait(token);
}
catch (OperationCanceledException)
{
// Throw immediately to be responsive. The
// alternative is to do one more item of work,
// and throw on next iteration, because
// IsCancellationRequested will be true.
Console.WriteLine("The wait operation was canceled.");
throw;
}
Console.Write("Working...");
// Simulating work.
Thread.SpinWait(500000);
Para um exemplo completo, consulte Como: Escutar solicitações de cancelamento têm alças de espera.
Ouvindo simultaneamente vários Tokens
Em alguns casos, pode ter um ouvinte ouvir simultaneamente vários tokens de cancelamento. Por exemplo, pode ter uma operação cancelável monitorar um símbolo de cancelamento interno com um token transmitido externamente como um argumento para um parâmetro de método. Para fazer isso, crie uma fonte de token vinculada pode unir duas ou mais símbolos em um token, conforme mostrado no exemplo a seguir.
Public Sub DoWork(ByVal externalToken As CancellationToken)
' Create a new token that combines the internal and external tokens.
Dim internalToken As CancellationToken = internalTokenSource.Token
Dim linkedCts As CancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken)
Using (linkedCts)
Try
DoWorkInternal(linkedCts.Token)
Catch e As OperationCanceledException
If e.CancellationToken = internalToken Then
Console.WriteLine("Operation timed out.")
ElseIf e.CancellationToken = externalToken Then
Console.WriteLine("Canceled by external token.")
externalToken.ThrowIfCancellationRequested()
End If
End Try
End Using
End Sub
public void DoWork(CancellationToken externalToken)
{
// Create a new token that combines the internal and external tokens.
this.internalToken = internalTokenSource.Token;
this.externalToken = externalToken;
using (CancellationTokenSource linkedCts =
CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken))
{
try
{
DoWorkInternal(linkedCts.Token);
}
catch (OperationCanceledException)
{
if (internalToken.IsCancellationRequested)
{
Console.WriteLine("Operation timed out.");
}
else if (externalToken.IsCancellationRequested)
{
Console.WriteLine("Cancelling per user request.");
externalToken.ThrowIfCancellationRequested();
}
}
}
}
Observe que você deve chamar Dispose na origem token vinculada quando tiver terminado com proprietário. Para um exemplo completo, consulte Como: Escutar várias solicitações de cancelamento.
Cooperação entre o código de biblioteca e o código de usuário
A estrutura unificada de cancelamento torna possível para o código da biblioteca cancelar o código de usuário e código do usuário cancelar o código da biblioteca de forma cooperativa. Cooperação suave depende de cada lado seguindo estas diretrizes:
Se o código da biblioteca fornece operações canceláveis, ele também deve fornecer métodos públicos que aceitam um token de cancelamento externos para que o código do usuário pode solicitar o cancelamento.
Se o código da biblioteca chama o código de usuário, o código da biblioteca deve interpretar um OperationCanceledException(externalToken) como cancelamento cooperativoe não necessariamente uma exceção de falha.
Representantes de usuário devem tentar responder a solicitações de cancelamento do código da biblioteca no momento oportuno.
System.Threading.Tasks.Taske System.Linq.ParallelEnumerable são exemplos de classes que segue essas diretrizes. Para obter mais informações, consulte Cancelamento da tarefae Como: Cancelar uma consulta PLINQ.