Lorsque j'utilise des tâches pour des charges de travail importantes/longues que je dois pouvoir annuler, j'utilise souvent un modèle similaire à celui-ci pour l'action que la tâche exécute:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
Log.Exception(ex);
throw;
}
}
L'opération OperationCanceledException ne doit pas être enregistrée en tant qu'erreur mais ne doit pas être avalée si la tâche doit passer à l'état annulé. Aucune autre exception n'a besoin d'être traitée au-delà de la portée de cette méthode.
Cela a toujours semblé un peu maladroit, et Visual Studio par défaut interrompra le lancement d'OperationCanceledException (bien que la fonction "Interruption sur l'utilisateur non géré" soit désormais désactivée pour OperationCanceledException en raison de mon utilisation de ce modèle).
Idéalement, je pense que j'aimerais pouvoir faire quelque chose comme ça:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (Exception ex) exclude (OperationCanceledException)
{
Log.Exception(ex);
throw;
}
}
c'est-à-dire avoir une sorte de liste d'exclusion appliquée à la capture mais sans prise en charge de la langue qui n'est pas actuellement possible (@ eric-lippert: c # vNext feature :)).
Une autre façon serait de poursuivre:
public void StartWork()
{
Task.Factory.StartNew(() => DoWork(cancellationSource.Token), cancellationSource.Token)
.ContinueWith(t => Log.Exception(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
}
public void DoWork(CancellationToken cancelToken)
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
mais je n'aime pas vraiment cela, car l'exception pourrait techniquement avoir plus d'une seule exception interne et vous n'avez pas autant de contexte lors de la journalisation de l'exception que dans le premier exemple (si je faisais plus que simplement la journaliser) ).
Je comprends que c'est un peu une question de style, mais vous vous demandez si quelqu'un a de meilleures suggestions?
Dois-je simplement m'en tenir à l'exemple 1?
Eamon
Donc quel est le problème? Jetez simplement le bloc catch (OperationCanceledException)
et définissez les suites appropriées:
var cts = new CancellationTokenSource();
var task = Task.Factory.StartNew(() =>
{
var i = 0;
try
{
while (true)
{
Thread.Sleep(1000);
cts.Token.ThrowIfCancellationRequested();
i++;
if (i > 5)
throw new InvalidOperationException();
}
}
catch
{
Console.WriteLine("i = {0}", i);
throw;
}
}, cts.Token);
task.ContinueWith(t =>
Console.WriteLine("{0} with {1}: {2}",
t.Status,
t.Exception.InnerExceptions[0].GetType(),
t.Exception.InnerExceptions[0].Message
),
TaskContinuationOptions.OnlyOnFaulted);
task.ContinueWith(t =>
Console.WriteLine(t.Status),
TaskContinuationOptions.OnlyOnCanceled);
Console.ReadLine();
cts.Cancel();
Console.ReadLine();
TPL distingue l'annulation et la faute. Par conséquent, l'annulation (c'est-à-dire le lancement de OperationCancelledException
dans le corps de la tâche) n'est pas une faute .
Le point principal: ne pas faire gérer les exceptions dans le corps de la tâche sans les relancer.
Voici comment vous gérez élégamment l'annulation des tâches:
var cts = new CancellationTokenSource( 5000 ); // auto-cancel in 5 sec.
Task.Run( () => {
cts.Token.ThrowIfCancellationRequested();
// do background work
cts.Token.ThrowIfCancellationRequested();
// more work
}, cts.Token ).ContinueWith( task => {
if ( !task.IsCanceled && task.IsFaulted ) // suppress cancel exception
Logger.Log( task.Exception ); // log others
} );
var cts = new CancellationTokenSource( 5000 ); // auto-cancel in 5 sec.
var taskToCancel = Task.Delay( 10000, cts.Token );
// do work
try { await taskToCancel; } // await cancellation
catch ( OperationCanceledException ) {} // suppress cancel exception, re-throw others
C # 6.0 a une solution pour cela .. exception de filtrage
int denom;
try
{
denom = 0;
int x = 5 / denom;
}
// Catch /0 on all days but Saturday
catch (DivideByZeroException xx) when (DateTime.Now.DayOfWeek != DayOfWeek.Saturday)
{
Console.WriteLine(xx);
}
Selon cet article de blog MSDN , vous devez intercepter OperationCanceledException
, par exemple.
async Task UserSubmitClickAsync(CancellationToken cancellationToken)
{
try
{
await SendResultAsync(cancellationToken);
}
catch (OperationCanceledException) // includes TaskCanceledException
{
MessageBox.Show(“Your submission was canceled.”);
}
}
Si votre méthode annulable se situe entre d'autres opérations annulables, vous devrez peut-être effectuer un nettoyage lors de l'annulation. Ce faisant, vous pouvez utiliser le bloc catch ci-dessus, mais assurez-vous de relancer correctement:
async Task SendResultAsync(CancellationToken cancellationToken)
{
try
{
await httpClient.SendAsync(form, cancellationToken);
}
catch (OperationCanceledException)
{
// perform your cleanup
form.Dispose();
// rethrow exception so caller knows you’ve canceled.
// DON’T “throw ex;” because that stomps on
// the Exception.StackTrace property.
throw;
}
}
Vous pouvez faire quelque chose comme ça:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException) when (cancelToken.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
Log.Exception(ex);
throw;
}
}