【问题标题】:How to retrieve the Exception of a ValueTask?如何检索 ValueTask 的异常?
【发布时间】:2018-10-22 09:30:58
【问题描述】:

即将推出的 C# 8 IAsyncEnumerable 的设计使用 ValueTaskValueTask<T> 将潜在的同步结果传达回消费者逻辑。它们都具有IsFaulted 属性,但与Task 不同的是,它们没有Exception 属性。

甚至可能有一个ValueTask 不包含正常的Task 并且处于故障或取消状态?

ValueTask<T>.Result 的文档表明在失败的任务上调用它会重新抛出包含的Exception。因此,以下代码对提取 Exception 有意义吗?

IAsyncEnumerable<int> asyncSequence = ...

ValueTask<bool> valueTask = asyncSequence.MoveNextAsync();

if (valueTask.IsFaulted) {
    // this has to work in a non-async method
    // there is no reason to block here or use the
    // async keyword
    try {
        var x = valueTask.Result;
    } catch (Exception ex) {
        // work with the exception
    }
}

ValueTask endTask = asyncSequence.DisposeAsync();

if (endTask.IsFaulted) {
    // there is no ValueTask.Result property
    // so the appoach above can't work
}

非泛型 ValueTask 没有 Result 属性。那么如何提取Exception呢?

一般来说,我认为应用AsTask 可以用于两种提取,但是,我认为它会导致分配,首先我理解ValueTask 的使用是有问题的。

【问题讨论】:

  • 你使用.Result而不是async/await有什么原因吗?你知道(取决于你的上下文)这个can deadlock
  • 以上内容基于您的评论this has to work in a non-async method 这似乎是您的根本问题,但如果没有更多上下文就很难判断
  • 没有办法从IValueTaskSource 检索异常,所以从那里我看到了两种方法:.AsTask().Exception.GetAwaiter().GetResult() 并捕获异常。令人惊讶的是,AsTask() 居然让底层的IValueTaskSource 抛出异常,捕获它,并从中创建一个故障任务:source.dot.net/#System.Private.CoreLib/shared/System/Threading/… 起初我很困惑,没有更优化的路径,但最终异常成本如此之高,以至于一旦你得到一个,你就不应该再关心微优化了

标签: c# async-await task


【解决方案1】:

TaskTask&lt;T&gt; 完成之前阻止的常用方法是调用Task.Wait() method

如果抛出异常,它将是System.AggregatedExceptionInnerExceptions property 将包含导致当前异常的异常集合。

从 .NET Framework 4.5 和 .NET Core 1.0 开始,还有 GetAwaiter() method,您可以在其中调用 GetResult() 的返回值,并将引发集合中的第一个异常。

但你永远不应该那样做!!!

你应该这样做:

// this has to work in a non-async method
try {
    var x = await valueTask;
} catch (Exception ex) {
    // work with the exception
}

ValueTaskValueTask&lt;T&gt; 用于避免在结果可能已经可用时进行堆分配。这并不意味着阻止该任务是安全的。

【讨论】:

  • 首先,我必须使用ValueTask,其中IsFaultedtrue,因此我不必阻止任何事情。第二个GetAwaiter 似乎已分配,因此我不会比使用AsTask 更好。第三,提取应该在没有await 的情况下工作,因为父方法不是并且不能标记async
  • 如果您不能使用async,请坚持使用IEnumerable
  • 也许我并不清楚,但使用ValueTask 是强制性的。我无法更改为 IEnumerable,因为它对我的情况没有语义意义,而且我尝试与之交互的 API 不是我定义的。
  • 你在哪里看到GetAwaiter分配?
  • 您不应该混合使用同步和异步 API。当它死锁时不要感到惊讶。
【解决方案2】:

corefx team 的回复表明,这确实是不可能直接实现的,并且由于需要更改伴随界面而无法轻松添加,这会带来复杂性。

正如我最初猜测的那样,建议的解决方法是使用AsTask 来获取Exception

IAsyncEnumerable<int> asyncSequence = ...

ValueTask<bool> valueTask = asyncSequence.MoveNextAsync();

if (valueTask.IsFaulted) {
    var ex = valueTask.AsTask().Exception;
    // work with the exception
}

ValueTask endTask = asyncSequence.DisposeAsync();

if (endTask.IsFaulted) {
    var ex = valueTask.AsTask().Exception;
    // work with the exception
}

【讨论】:

    【解决方案3】:

    最好的办法是使用 try 语句并尝试在那里捕获异常,然后再处理它... 对于不包含正常任务的 ValueTask,我认为您可以使用 lambda 方法来解决。

    try {
        var x = Task.Run(()=>{ ... });
    }
    catch (Exception ex) {
        // Do something with exception.
    }
    

    或者不管 ValueTask 的情况如何。考虑到 C# 在过去的构建中大量关注异步任务,可以肯定地说您将使用 lambda 和事件调用。

    private void foo(ValueTask task) {
            lock (threadObj) {
                var x = Task.Run(()=> { return task.doWork(); }
                if (x == true) {
                    // then do other work...
                }
                else {
                    // handle 'exception'...
                }
            }
    }
    

    【讨论】:

    • 这个答案对我来说毫无意义。起点是我有一个 ValueTask 通过某种第 3 方方法返回给我。另外,关于ValueTask,您指的是什么“lambdas”和“事件调用”?
    • 对不起,我没有多大意义。但本质上,lambdas 是用括号和括号括起来的方法。即 foo(object sender, EventArgs e) 等于 (s,e)=>{...} 并且事件调用正在利用事件并调用它们。即 someEvent?.Invoke((s,e)=>{...}) 这个例子使用 lambdas 和一个事件调用来调用一个方法作为参数......这种编程通常在 System.Linq 中使用。因此,我的意思是,一旦你有了 valueTask,就可以在线程安全的上下文中使用它,这可以让你捕获程序表达的不需要的操作。
    • 我的问题与 lambdas 或创建任务无关。我已经从某个我无法控制的地方获得了ValueTask,并且必须摆脱它。
    猜你喜欢
    • 2016-03-17
    • 1970-01-01
    • 2020-04-07
    • 2015-05-06
    • 1970-01-01
    • 2021-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多