Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/329.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 优雅地处理任务取消_C#_Exception_Task Parallel Library - Fatal编程技术网

C# 优雅地处理任务取消

C# 优雅地处理任务取消,c#,exception,task-parallel-library,C#,Exception,Task Parallel Library,在为需要取消的大型/长时间运行的工作负载使用任务时,我经常使用类似于此的模板来执行任务执行的操作: public void DoWork(CancellationToken cancelToken) { try { //do work cancelToken.ThrowIfCancellationRequested(); //more work } catch (OperationCanceledException)

在为需要取消的大型/长时间运行的工作负载使用任务时,我经常使用类似于此的模板来执行任务执行的操作:

public void DoWork(CancellationToken cancelToken)
{
    try
    {
        //do work
        cancelToken.ThrowIfCancellationRequested();
        //more work
    }
    catch (OperationCanceledException)
    {
        throw;
    }
    catch (Exception ex)
    {
        Log.Exception(ex);
        throw;
    }
}
OperationCanceledException
不应被记录为错误,但如果任务要转换为取消状态,则不能被忽略。除此方法的范围外,不需要处理任何其他异常

这总是让人感觉有点笨拙,默认情况下,visual studio会在抛出
OperationCanceledException
时中断(尽管由于使用了此模式,我现在已将
OperationCanceledException
的“用户未处理时中断”关闭)

理想情况下,我认为我希望能够做到以下几点:

public void DoWork(CancellationToken cancelToken)
{
    try
    {
        //do work
        cancelToken.ThrowIfCancellationRequested();
        //more work
    }
    catch (Exception ex) exclude (OperationCanceledException)
    {
        Log.Exception(ex);
        throw;
    }
}
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;
    }
}
i、 e.将某种排除列表应用于捕获,但没有当前不可能的语言支持(@eric lippert:c#vNext feature:))

另一种方式是继续:

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
}
但我并不喜欢这样,因为从技术上讲,异常可能不止一个内部异常,而且在记录异常时没有第一个示例中那样多的上下文(如果我不仅仅是记录它的话)

我知道这是一个风格的问题,但不知道是否有人有更好的建议


我必须坚持示例1吗?

我不完全确定您在这里想要实现什么,但我认为以下模式可能会有所帮助

public void DoWork(CancellationToken cancelToken)
{
    try
    {
        //do work
        cancelToken.ThrowIfCancellationRequested();
        //more work
    }
    catch (OperationCanceledException) {}
    catch (Exception ex)
    {
        Log.Exception(ex);
    }
}
您可能已经注意到我已经从这里删除了throw语句。这不会抛出异常,但会忽略它

如果你想做别的事,请告诉我

还有另一种方式,它与您在代码中展示的方式非常接近

    catch (Exception ex)
    {
        if (!ex.GetType().Equals(<Type of Exception you don't want to raise>)
        {
            Log.Exception(ex);

        }
    }
catch(异常示例)
{

如果(!ex.GetType().Equals(那么,有什么问题吗?只需扔掉
catch(OperationCanceledException)
块,并设置适当的延续:

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区分取消和故障。因此,取消(即在任务体内抛出
operationCanceledException
)不是故障

要点:在不重新抛出异常的情况下,不要处理任务体中的异常。

C#6.0有一个解决方案


以下是您如何优雅地处理任务取消:

处理“开火并忘记”任务 处理等待任务完成/取消 根据,您应该捕获
操作取消异常
,例如

async Task UserSubmitClickAsync(CancellationToken cancellationToken)
{
   try
   {
      await SendResultAsync(cancellationToken);
   }
   catch (OperationCanceledException) // includes TaskCanceledException
   {
      MessageBox.Show(“Your submission was canceled.”);
   }
}
如果您的可取消方法介于其他可取消操作之间,则您可能需要在取消时执行清理。执行此操作时,您可以使用上面的catch块,但请确保正确地重试:

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; 
   }
}

你可以这样做:

public void DoWork(CancellationToken cancelToken)
{
    try
    {
        //do work
        cancelToken.ThrowIfCancellationRequested();
        //more work
    }
    catch (Exception ex) exclude (OperationCanceledException)
    {
        Log.Exception(ex);
        throw;
    }
}
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;
    }
}

这:
catch(OperationCanceledException){
将任务的状态设置为
RanToCompletion
,而不是
cancelled
。当这是一个显著的差异时,会有一些用例。我使用continuation方法的唯一问题是我失去了上下文。例如,如果我正在处理一个项目列表,并且我需要记录异常发生时我在集合中的距离rown.我确实在我原来的问题中遗漏了一些东西,我现在已经确认了,这是对异常ex的一次重新调用,以允许任务转换到故障状态。下面有一些更简单的答案。我使用了一种解决方案,其中日志框架对一些异常执行不同的操作。即忽略操作取消异常,展平AggregateException,只记录InvalidOperationException etc的innerException关键字实际上是'when'not'if'。(OP)的语法是:catch(Exception ex)when(!(ex是OperationCanceledException))我更喜欢更严格的检查:
catch(OperationCanceledException e)when(e.CancellationToken==cancelToken)
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;
    }
}