如何处理IAsyncEnumerable作为返回类型的方法中的错误



我有一个API端点:

[HttpGet("api/query")]
public async IAsyncEnumerable<dynamic> Query(string name)
{    
await foreach(var item in _myService.CallSomethingReturningAsyncStream(name))
{
yield return item;
}
}

我希望能够在ArgumentException的情况下返回类似"错误的请求"响应。

如果我尝试使用try-catch块,我得到错误:

CS1626:不能在带有catch的try块体中产生值条款

请注意,它是一个API端点方法,所以错误处理应该在理想的相同方法中,而不需要制作额外的中间件。

如果需要,CallSomethingReturningAsyncStream方法的粗略实现:

public async IAsyncEnumerable<dynamic> CallSomethingReturningAsyncStream(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Name is missing", nameof(name));
(...)
foreach (var item in result)
{
yield return item;
}
}

你可以安装System.Interactive.Async包,然后这样做:

[HttpGet("api/query")]
public IAsyncEnumerable<dynamic> Query(string name)
{
return AsyncEnumerableEx.Defer(() => _myService.CallSomethingReturningAsyncStream(name))
.Catch<dynamic, Exception>(ex =>
AsyncEnumerableEx.Return<dynamic>($"Bad request: {ex.Message}"));
}

Defer算子的签名:

// Returns an async-enumerable sequence that invokes the specified
// factory function whenever a new observer subscribes.
public static IAsyncEnumerable<TSource> Defer<TSource>(
Func<IAsyncEnumerable<TSource>> factory)

Catch算子的签名:

// Continues an async-enumerable sequence that is terminated by
// an exception of the specified type with the async-enumerable sequence
// produced by the handler.
public static IAsyncEnumerable<TSource> Catch<TSource, TException>(
this IAsyncEnumerable<TSource> source,
Func<TException, IAsyncEnumerable<TSource>> handler);

Return操作符的签名:

// Returns an async-enumerable sequence that contains a single element.
public static IAsyncEnumerable<TValue> Return<TValue>(TValue value)

Defer可能看起来很肤浅,但是在_myService.CallSomethingReturningAsyncStream同步抛出的情况下是需要的。如果该方法是作为async迭代器实现的,它将永远不会同步抛出,因此您可以省略Defer.

您必须拆分方法。提取异步处理的部分:

private async IAsyncEnumerable<dynamic> ProcessData(TypeOfYourData data)
{    
await foreach(var item in data)
{
yield return item;
}
}

然后在API方法do:

[HttpGet("api/query")]
public IActionResult Query(string name)
{    
TypeOfYourData data;
try {
data = _myService.CallSomethingReturningAsyncStream(name);
}
catch (...) {
// do what you need
return BadRequest();
}
return Ok(ProcessData(data));
}

或者你可以把整个东西移到单独的方法中:

[HttpGet("api/query")]
public IActionResult Query(string name)
{           
try {
return Ok(TheMethodYouMovedYourCurrentCodeTo);
}
catch (...) {
// do what you need
return BadRequest();
}
}

它当然只捕获在实际异步枚举开始之前抛出的异常,但据我所知,这对于您的用例来说是很好的。在异步枚举开始后返回错误的请求是不可能的,因为响应已经被发送到客户端。

更新:你提到你的服务代码是:

public async IAsyncEnumerable<dynamic> CallSomethingReturningAsyncStream(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Name is missing", nameof(name));

foreach (var item in result)
{
yield return item;
}
}

它与您当前的api方法有相同的问题。通常,将这类方法分成两部分是一种很好的做法——第一部分执行验证,然后返回第二部分。例如,让我们考虑以下代码:

public class Program {
static void Main() {
try {
CallSomethingReturningAsyncStream(null);
}
catch (Exception ex) {
Console.WriteLine(ex);
}
}
public static async IAsyncEnumerable<string> CallSomethingReturningAsyncStream(string name) {
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Name is missing", nameof(name));
await Task.Delay(100);
yield return "1";
}
}

它将在没有任何异常的情况下完成,因为CallSomethingReturningAsyncStream内部的代码被编译器转换为状态机,并且只在枚举结果时执行,包括验证参数的部分。

但是,如果您像这样更改方法:

public class Program {
static void Main() {
try {
CallSomethingReturningAsyncStream(null);
}
catch (Exception ex) {
Console.WriteLine(ex);
}
}
public static IAsyncEnumerable<string> CallSomethingReturningAsyncStream(string name) {
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Name is missing", nameof(name));
return CallSomethingReturningAsyncStreamInternal(name);
}

// you can also put that inside method above,
// as local function
private static async IAsyncEnumerable<string> CallSomethingReturningAsyncStreamInternal(string name) {
await Task.Delay(100);
yield return "1";
}
}

它将做同样的事情,但是现在方法同步验证参数,然后转到异步部分。这段代码会抛出一个被捕获的参数异常。

因此,我建议您的代码遵循此最佳实践,这也将解决您的问题所述的问题。没有理由将抛出异常延迟到发生枚举时才抛出,因为很明显实参是无效的,无论如何也不可能进行枚举。

最新更新