【问题标题】:Exception is not caught at Cancelation of Task.Run取消 Task.Run 时未捕获异常
【发布时间】:2015-02-23 22:03:00
【问题描述】:

我有一个班级 Worker 正在做一些工作(模拟工作量):

public class Worker
    { ...

public void DoWork(CancellationToken ct)
        {
            for (int i = 0; i < 10; i++)
            {
                ct.ThrowIfCancellationRequested();
                Thread.Sleep(2000);
            }
        }

现在我想在 Task.Run 中使用此方法(来自我的 Windows 窗体应用程序,单击按钮),可以取消:

private CancellationTokenSource _ctSource;

try
            {
                Task.Run(() =>
                {
                    _worker.DoWork(_ctSource.Token);
                },_ctSource.Token);
            }
            catch (AggregateException aex)
            {
                String g = aex.Message;
            }
            catch (OperationCanceledException ex)
            {
                String g = ex.Message;
            }
            catch (Exception ex)
            {
                String g = ex.Message;
            }

但是当任务启动时,我不能用_ctSource.Cancel()取消它; 我在 Visual Studio 中收到一个错误,即 OperationCanceledException 未处理!

但是我用 try-catch-clause 包围了 Task.Run 调用! Worker 对象中发生的异常应该抛出还是不抛出? 有什么问题?

【问题讨论】:

  • OperationHandledException?你确定吗?我从来没有听说过这样的事情。
  • 我的意思是 OperationCanceledException :-)
  • I get an error in visual studio that the OperationCanceledException is not handled! 只需点击F5 并忽略它。当你在 VS 之外运行它时,你不会得到它。顺便说一句:您无法像在代码中那样捕获任务的异常。您创建一个任务并退出 try 块。 ContinueWith 可能会有所帮助。

标签: c# .net exception task-parallel-library


【解决方案1】:

您的Task.Run 调用创建了任务,然后立即返回。它永远不会抛出。但它创建的任务可能会失败或稍后被取消。

这里有几种解决方案:

  • 使用await:

    await Task.Run(...)
    
  • 根据失败/取消情况附加一个延续:

    var task = Task.Run(...);
    task.ContinueWith(t => ..., TaskContinuationOptions.OnlyOnCanceled);
    task.ContinueWith(t => ..., TaskContinuationOptions.OnlyOnFaulted);
    
  • 在失败时附加一个延续:

    Task.Run(...).ContinueWith(t => ..., TaskContinuationOptions.NotOnRanToCompletion);
    

您可以/应该使用的解决方案取决于周围的代码。

【讨论】:

  • 您的解决方案无效,我尝试了 await 和 task.ContinueWith() 仍然收到此错误
  • 这很好用(我赞成)。您遇到的问题是您试图捕获 Task.Run() 周围的异常,当异常上升时,主线程的执行早已超过您的 try/catch 块。这就是为什么异步任务不会在主线程上抛出异常的原因。您需要检查异常之后任务中的代码已经运行(当任务结束时),要么通过继续任务,要么直接检查返回的任务。
  • 好的,现在我测试它并将 try-catch-block 放入 Task.Run 中,现在它捕获了!!
【解决方案2】:

您需要更新令牌
私人 CancellationTokenSource _ctSource = new CancellationTokenSource();

为什么要在 DoWork 中提出期望?
一个线程的异常不会冒泡启动该线程的另一个线程。

Cancellation in Managed Threads

【讨论】:

    【解决方案3】:

    如果并行任务抛出异常,它将返回执行并拥有它的Exception 属性(作为AggregateException,您应该检查它的InnerException)集(以及它的IsCanceled 或@987654325 @ 属性设置为 true)。我的一个项目中的一些最小示例代码将异常升级到主线程:

      var t = new Task(Initialize);
      t.Start();
      while (!t.IsCompleted && !t.IsFaulted)
      {
        // Do other work in the main thread
      }
      if (t.IsFaulted)
      {
        if (t.Exception != null)
        {
          if(t.Exception.InnerException != null)
            throw t.Exception.InnerException;
        }
        throw new InvalidAsynchronousStateException("Initialization failed for an unknown reason");
      }
    

    如果您使用CancellationTokenSource,应该很容易增强它以检查IsCanceled(而不是IsFaulted

    您也可以在我的项目中使用Task.Wait() 而不是while 循环...在这种情况下,使用while 循环似乎更合适,但您需要等待Task 结束以某种方式。

    如果您使用Task.Run(),您可以使用.ContinueWith(Task),它将传入原始任务(您可以在其中检查IsFaultedIsCanceled),或者让它仅在错误执行时运行,在您的会的。

    【讨论】:

      猜你喜欢
      • 2017-04-17
      • 2013-08-11
      • 1970-01-01
      • 1970-01-01
      • 2010-09-28
      • 2012-05-31
      相关资源
      最近更新 更多