Cómo: Cancelar una consulta PLINQ
En los siguientes ejemplos se muestran dos maneras de cancelar una consulta PLINQ. En el primer ejemplo se muestra cómo cancelar una consulta que consta principalmente de recorridos de datos. En el segundo ejemplo se muestra cómo cancelar una consulta que contiene una función de usuario que resulta cara en los cálculos.
Nota |
---|
Cuando está habilitada la opción "Solo mi código", Visual Studio se interrumpe en la línea que produce la excepción y muestra el mensaje de error "Excepción no controlada por el código de usuario". Este error es benigno.Puede presionar F5 para continuar y ver el comportamiento de control de excepciones que se muestra en los ejemplos siguientes.Para evitar que Visual Studio se interrumpa con el primer error, desactive la casilla "Solo mi código" bajo Herramientas, Opciones, Depuración, General. Este ejemplo está diseñado para mostrar el uso y podría no ejecutarse más rápidamente que la consulta secuencial equivalente de LINQ to Objects.Para obtener más información sobre la aceleración, vea Introducción a la velocidad en PLINQ. |
Ejemplo
Class Program
Private Shared Sub Main(ByVal args As String())
Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()
Dim cs As New CancellationTokenSource()
' Start a new asynchronous task that will cancel the
' operation from another thread. Typically you would call
' Cancel() in response to a button click or some other
' user interface event.
Task.Factory.StartNew(Sub()
UserClicksTheCancelButton(cs)
End Sub)
Dim results As Integer() = Nothing
Try
results = (From num In source.AsParallel().WithCancellation(cs.Token) _
Where num Mod 3 = 0 _
Order By num Descending _
Select num).ToArray()
Catch e As OperationCanceledException
Console.WriteLine(e.Message)
Catch ae As AggregateException
If ae.InnerExceptions IsNot Nothing Then
For Each e As Exception In ae.InnerExceptions
Console.WriteLine(e.Message)
Next
End If
End Try
If results IsNot Nothing Then
For Each item In results
Console.WriteLine(item)
Next
End If
Console.WriteLine()
Console.ReadKey()
End Sub
Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)
' Wait between 150 and 500 ms, then cancel.
' Adjust these values if necessary to make
' cancellation fire while query is still executing.
Dim rand As New Random()
Thread.Sleep(rand.[Next](150, 350))
cs.Cancel()
End Sub
End Class
namespace PLINQCancellation_1
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
int[] source = Enumerable.Range(1, 10000000).ToArray();
CancellationTokenSource cs = new CancellationTokenSource();
// Start a new asynchronous task that will cancel the
// operation from another thread. Typically you would call
// Cancel() in response to a button click or some other
// user interface event.
Task.Factory.StartNew(() =>
{
UserClicksTheCancelButton(cs);
});
int[] results = null;
try
{
results = (from num in source.AsParallel().WithCancellation(cs.Token)
where num % 3 == 0
orderby num descending
select num).ToArray();
}
catch (OperationCanceledException e)
{
Console.WriteLine(e.Message);
}
catch (AggregateException ae)
{
if (ae.InnerExceptions != null)
{
foreach (Exception e in ae.InnerExceptions)
Console.WriteLine(e.Message);
}
}
if (results != null)
{
foreach (var v in results)
Console.WriteLine(v);
}
Console.WriteLine();
Console.ReadKey();
}
static void UserClicksTheCancelButton(CancellationTokenSource cs)
{
// Wait between 150 and 500 ms, then cancel.
// Adjust these values if necessary to make
// cancellation fire while query is still executing.
Random rand = new Random();
Thread.Sleep(rand.Next(150, 350));
cs.Cancel();
}
}
}
El marco de PLINQ no incluye una única excepción OperationCanceledException en System.AggregateException; OperationCanceledException se debe controlar en un bloque catch independiente. Si uno o más delegados de usuario inician una excepción OperationCanceledException(externalCT) (mediante una estructura System.Threading.CancellationToken externa) pero ninguna otra excepción, y la consulta se definió como AsParallel().WithCancellation(externalCT), PLINQ emitirá una única excepción OperationCanceledException (externalCT) en lugar de System.AggregateException. Sin embargo, si un delegado de usuario inicia una excepción OperationCanceledException y otro delegado inicia otro tipo de excepción, ambas excepciones se incluyen en AggregateException.
A continuación se proporciona una orientación general sobre la cancelación:
Si realiza una cancelación del delegado de usuario, debería informar a PLINQ sobre la estructura CancellationToken externa e iniciar una excepción OperationCanceledException (externalCT).
Si se produce la cancelación y no se inicia ninguna otra excepción, debería controlar una clase OperationCanceledException en lugar de AggregateException.
En el siguiente ejemplo se muestra cómo controlar la cancelación cuando se cuenta con una función cara en cálculos en el código de usuario.
Class Program2
Private Shared Sub Main(ByVal args As String())
Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()
Dim cs As New CancellationTokenSource()
' Start a new asynchronous task that will cancel the
' operation from another thread. Typically you would call
' Cancel() in response to a button click or some other
' user interface event.
Task.Factory.StartNew(Sub()
UserClicksTheCancelButton(cs)
End Sub)
Dim results As Double() = Nothing
Try
results = (From num In source.AsParallel().WithCancellation(cs.Token) _
Where num Mod 3 = 0 _
Select [Function](num, cs.Token)).ToArray()
Catch e As OperationCanceledException
Console.WriteLine(e.Message)
Catch ae As AggregateException
If ae.InnerExceptions IsNot Nothing Then
For Each e As Exception In ae.InnerExceptions
Console.WriteLine(e.Message)
Next
End If
End Try
If results IsNot Nothing Then
For Each item In results
Console.WriteLine(item)
Next
End If
Console.WriteLine()
Console.ReadKey()
End Sub
' A toy method to simulate work.
Private Shared Function [Function](ByVal n As Integer, ByVal ct As CancellationToken) As Double
' If work is expected to take longer than 1 ms
' then try to check cancellation status more
' often within that work.
For i As Integer = 0 To 4
' Work hard for approx 1 millisecond.
Thread.SpinWait(50000)
' Check for cancellation request.
If ct.IsCancellationRequested Then
Throw New OperationCanceledException(ct)
End If
Next
' Anything will do for our purposes.
Return Math.Sqrt(n)
End Function
Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)
' Wait between 150 and 500 ms, then cancel.
' Adjust these values if necessary to make
' cancellation fire while query is still executing.
Dim rand As New Random()
Thread.Sleep(rand.[Next](150, 350))
Console.WriteLine("Press 'c' to cancel")
If Console.ReadKey().KeyChar = "c"c Then
cs.Cancel()
End If
End Sub
End Class
namespace PLINQCancellation_2
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
int[] source = Enumerable.Range(1, 10000000).ToArray();
CancellationTokenSource cs = new CancellationTokenSource();
// Start a new asynchronous task that will cancel the
// operation from another thread. Typically you would call
// Cancel() in response to a button click or some other
// user interface event.
Task.Factory.StartNew(() =>
{
UserClicksTheCancelButton(cs);
});
double[] results = null;
try
{
results = (from num in source.AsParallel().WithCancellation(cs.Token)
where num % 3 == 0
select Function(num, cs.Token)).ToArray();
}
catch (OperationCanceledException e)
{
Console.WriteLine(e.Message);
}
catch (AggregateException ae)
{
if (ae.InnerExceptions != null)
{
foreach (Exception e in ae.InnerExceptions)
Console.WriteLine(e.Message);
}
}
if (results != null)
{
foreach (var v in results)
Console.WriteLine(v);
}
Console.WriteLine();
Console.ReadKey();
}
// A toy method to simulate work.
static double Function(int n, CancellationToken ct)
{
// If work is expected to take longer than 1 ms
// then try to check cancellation status more
// often within that work.
for (int i = 0; i < 5; i++)
{
// Work hard for approx 1 millisecond.
Thread.SpinWait(50000);
// Check for cancellation request.
ct.ThrowIfCancellationRequested();
}
// Anything will do for our purposes.
return Math.Sqrt(n);
}
static void UserClicksTheCancelButton(CancellationTokenSource cs)
{
// Wait between 150 and 500 ms, then cancel.
// Adjust these values if necessary to make
// cancellation fire while query is still executing.
Random rand = new Random();
Thread.Sleep(rand.Next(150, 350));
Console.WriteLine("Press 'c' to cancel");
if (Console.ReadKey().KeyChar == 'c')
cs.Cancel();
}
}
}
Al controlar la cancelación en el código de usuario, no es necesario usar WithCancellation<TSource> en la definición de la consulta. Sin embargo, recomendamos usarlo ya que WithCancellation<TSource> no tiene ningún efecto en el rendimiento de la consulta y permite controlar la cancelación con operadores de consulta y con el código de usuario.
Para garantizar la capacidad de respuesta del sistema, se recomienda comprobar la cancelación alrededor de una vez por milisegundo; también se considera aceptable, sin embargo, un período máximo de 10 milisegundos. Esta frecuencia no tiene por qué tener un impacto negativo en el rendimiento del código.
Cuando se elimina un enumerador, por ejemplo cuando el código sale de un bucle foreach (For Each en Visual Basic) que está iterando en los resultados de la consulta, la consulta se cancela pero no se inicia ninguna excepción.
Vea también
Referencia
Conceptos
Historial de cambios
Fecha |
Historial |
Motivo |
---|---|---|
Mayo de 2010 |
Nota agregada sobre el uso frente a la aceleración. |
Comentarios de los clientes. |