c# - run - ¿Puedo cancelar StreamReader.ReadLineAsync con un CancellationToken?
cancellationtoken c# (3)
No puede cancelar Streamreader.ReadLineAsync()
. En mi humilde opinión esto es porque leer una sola línea debe ser muy rápido. Pero puede evitar fácilmente que Console.WriteLine()
ocurra utilizando una variable de tarea separada.
La comprobación de ct.IsCancellationRequested
también es redundand ya que ct.ThrowIfCancellationRequested()
solo lanzará si se solicita la cancelación.
StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
ct.ThrowIfCancellationRequested();
string line = await reader.ReadLineAsync());
ct.ThrowIfCancellationRequested();
Console.WriteLine(line);
}
Cuando cancelo mi método async con el siguiente contenido llamando al método Cancel()
de CancellationTokenSource
, se detendrá eventualmente. Sin embargo, desde la línea Console.WriteLine(await reader.ReadLineAsync());
toma bastante tiempo para completarse, traté de pasar mi CancellationToken a ReadLineAsync()
también (esperando que devuelva una cadena vacía) para que el método sea más receptivo a mi llamada a Cancel()
. Sin embargo, no pude pasar un CancellationToken
a ReadLineAsync()
.
¿Puedo cancelar una llamada a Console.WriteLine()
o Streamreader.ReadLineAsync()
y, de ser así, cómo lo hago?
¿Por qué ReadLineAsync()
no acepta un CancellationToken
? Pensé que era una buena práctica dar a los métodos Async un parámetro opcional CancellationToken
incluso si el método aún se completa después de ser cancelado.
StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
if (ct.IsCancellationRequested){
ct.ThrowIfCancellationRequested();
break;
}
else
{
Console.WriteLine(await reader.ReadLineAsync());
}
}
Actualización Como se indica en los comentarios a continuación, la llamada Console.WriteLine()
sola ya ocupaba varios segundos debido a una cadena de entrada con formato deficiente de 40,000 caracteres por línea. Romper esto resuelve mis problemas de tiempo de respuesta, pero todavía estoy interesado en cualquier sugerencia o solución alternativa sobre cómo cancelar esta declaración de larga duración si por alguna razón se pretendía escribir 40,000 caracteres en una línea (por ejemplo, cuando se tira toda la cuerda a un archivo).
No puede cancelar la operación a menos que sea cancelable. Puede usar el método de extensión WithCancellation
para que su flujo de código se comporte como si se hubiera cancelado, pero el subyacente todavía se ejecutaría:
public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
return task.IsCompleted // fast-path optimization
? task
: task.ContinueWith(
completedTask => completedTask.GetAwaiter().GetResult(),
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
Uso:
await task.WithCancellation(cancellationToken);
No puede cancelar Console.WriteLine
y no es necesario. Es instantáneo si tienes una string
tamaño razonable.
Acerca de la guía: si su implementación no admite la cancelación, no debería aceptar un token, ya que envía un mensaje mixto.
Si tiene una cadena enorme para escribir en la consola, no debe usar Console.WriteLine
. Puede escribir la cadena en un carácter a la vez y hacer que ese método sea cancelable:
public void DumpHugeString(string line, CancellationToken token)
{
foreach (var character in line)
{
token.ThrowIfCancellationRequested();
Console.Write(character);
}
Console.WriteLine();
}
Una solución aún mejor sería escribir en lotes en lugar de caracteres individuales. Aquí hay una implementación que usa el Batch
MoreLinq
:
public void DumpHugeString(string line, CancellationToken token)
{
foreach (var characterBatch in line.Batch(100))
{
token.ThrowIfCancellationRequested();
Console.Write(characterBatch.ToArray());
}
Console.WriteLine();
}
Entonces, en conclusión:
var reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
DumpHugeString(await reader.ReadLineAsync().WithCancellation(token), token);
}
Generalicé esta respuesta a esto:
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken, Action action, bool useSynchronizationContext = true)
{
using (cancellationToken.Register(action, useSynchronizationContext))
{
try
{
return await task;
}
catch (Exception ex)
{
if (cancellationToken.IsCancellationRequested)
{
// the Exception will be available as Exception.InnerException
throw new OperationCanceledException(ex.Message, ex, cancellationToken);
}
// cancellation hasn''t been requested, rethrow the original Exception
throw;
}
}
}
Ahora puede usar su token de cancelación en cualquier método asincrónico cancelable. Por ejemplo, WebRequest.GetResponseAsync:
var request = (HttpWebRequest)WebRequest.Create(url);
using (var response = await request.GetResponseAsync())
{
. . .
}
se convertirá:
var request = (HttpWebRequest)WebRequest.Create(url);
using (WebResponse response = await request.GetResponseAsync().WithCancellation(CancellationToken.None, request.Abort, true))
{
. . .
}
Ver ejemplo http://pastebin.com/KauKE0rW