I'm jugando con estas tareas de Windows 8 WinRT, y I'm tratando de cancelar una tarea utilizando el método de abajo, y funciona hasta cierto punto. El método CancelNotification SÍ se llama, lo que hace pensar que la tarea se ha cancelado, pero en segundo plano la tarea sigue ejecutándose y, una vez finalizada, el estado de la tarea es siempre completada y nunca cancelada. ¿Hay alguna forma de detener completamente la tarea cuando se cancela?
private async void TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.Token.Register(CancelNotification);
source.CancelAfter(TimeSpan.FromSeconds(1));
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token);
await task;
if (task.IsCompleted)
{
MessageDialog md = new MessageDialog(task.Result.ToString());
await md.ShowAsync();
}
else
{
MessageDialog md = new MessageDialog("Uncompleted");
await md.ShowAsync();
}
}
private int slowFunc(int a, int b)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
}
return a + b;
}
private void CancelNotification()
{
}
Lee sobre Cancellation (que se introdujo en .NET 4.0 y no ha cambiado mucho desde entonces) y el Task-Based Asynchronous Pattern, que proporciona directrices sobre cómo usar CancellationToken
con métodos async
.
En resumen, pasas un CancellationToken
a cada método que soporte cancelación, y ese método debe comprobarlo periódicamente.
private async Task TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(1));
Task<int> task = Task.Run(() => slowFunc(1, 2, source.Token), source.Token);
// (A canceled task will raise an exception when awaited).
await task;
}
private int slowFunc(int a, int b, CancellationToken cancellationToken)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
if (i % 1000 == 0)
cancellationToken.ThrowIfCancellationRequested();
}
return a + b;
}
O, para evitar modificar slowFunc
(digamos que no tienes acceso al código fuente, por ejemplo):
var source = new CancellationTokenSource(); //original code
source.Token.Register(CancelNotification); //original code
source.CancelAfter(TimeSpan.FromSeconds(1)); //original code
var completionSource = new TaskCompletionSource<object>(); //New code
source.Token.Register(() => completionSource.TrySetCanceled()); //New code
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token); //original code
//original code: await task;
await Task.WhenAny(task, completionSource.Task); //New code
También puede utilizar métodos de extensión agradable de https://github.com/StephenCleary/AsyncEx y que se ve tan simple como:
await Task.WhenAny(task, source.Token.AsTask());
Sólo quiero añadir a la respuesta ya aceptada. Yo estaba atascado en esto, pero yo iba una ruta diferente en el manejo del evento completo. En lugar de ejecutar await, añado un manejador completado a la tarea.
Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);
Donde el manejador del evento se ve así
private void CommentLoadComplete(IAsyncAction sender, AsyncStatus status )
{
if (status == AsyncStatus.Canceled)
{
return;
}
CommentsItemsControl.ItemsSource = Comments.Result;
CommentScrollViewer.ScrollToVerticalOffset(0);
CommentScrollViewer.Visibility = Visibility.Visible;
CommentProgressRing.Visibility = Visibility.Collapsed;
}
Con esta ruta, todo el manejo ya está hecho para usted, cuando la tarea se cancela sólo desencadena el controlador de eventos y se puede ver si se canceló allí.