使用 Task<T>.IsFaulted 表示 IO 故障而不传播异常
Using Task<T>.IsFaulted to denote IO failures without propagating exceptions
Before/without Task<>
s,我正在使用如下所示的包装器对象来封装可能失败的 I/O 操作的结果,而不会将异常传播到调用堆栈:
public class FetchResult<T>
{
public readonly bool Success;
public readonly T Item;
public FetchResult(bool success, T item)
{
this.Success = success;
this.Item = item;
}
}
我会这样使用它:
var userResult = Get("robert.paulson@fightclub.com");
if(!userResult.Success)
// abort, or something
...
public FetchResult<User> Get(string email)
{
try
{
// go to database here, and get the User
return new FetchResult(true, new User());
}
catch
{
// log exception
return new FetchResult(false, null);
}
}
作为一个模型,这对我来说非常有用,因为它允许我在不使用 try/catch
作为程序控制流的情况下有效地管理异常,并为我提供了简单而细粒度的优雅服务降级。
然而,随着 Task<>
的出现,我很容易得到:
public Task<FetchResult<User>> GetAsync(string email)
这似乎失控了。
看到我到处都迁移到 async
,我正在考虑这样做:
public Task<User> GetAsync(string email)
哪个 id 允许我做类似的事情:
var userTask = GetAsync("robert.paulson@fightclub.com");
await userTask;
if(userTask.IsFaulted) // (*) - see below
// abort, or something
但是如果我的 GetAsync
方法 returns:
return Task<User>.FromException(new Exception());
在等待((*)
注释所在的位置)之后实际返回的内容似乎是已完成的 Task
,其结果是 Task<User>
,这是错误的。
为什么我在这种情况下得到了一个嵌套任务,我是否缺少一些语法糖来使整个事件更整洁?
Task.IsFaulted
表示that the Task completed due to an unhandled exception。所以我不认为你可以用不同的方式设置它而不是让异常未被捕获。
public async Task<User> GetAsync(string email)
{
// go to database here, and get the User
return new User();
}
或者捕获、记录并重新抛出:
public async Task<User> GetAsync(string email)
{
try
{
// go to database here, and get the User
return new User();
}
catch
{
// log exception
throw;
}
}
我相信你是说你的 GetAsync
是 return 嵌套任务,因为你有这样的事情:
public async Task GetAsync(string email)
{
// .... somewhere in the code
return Task.FromException(new Exception());
}
但是当您使用 async
关键字声明一个方法时,C# 魔法会负责将其内容包装在 Task
中。因此,在这种情况下,您实际上最终得到的任务 return 是另一项任务 - Task<Task<T>>
。如果删除 async
关键字,它将 return Task<T>
但显然你最终会使用标准同步方法。
有点不寻常,但您可以探索 Task.WhenAny method does not throw exception (see ) 这样的事实
var userTask = GetAsync("robert.paulson@fightclub.com");
await Task.WaitAny(userTask);
if(userTask.IsFaulted) // (*) - see below
// abort, or something
任务库中有两种方法 FromException()
和 FromException<TResult>()
都可以通过 Task
和 Task<TResult>
使用。
public static Task FromException(Exception exception)
{
return FromException<VoidTaskResult>(exception);
}
public static Task<TResult> FromException<TResult>(Exception exception)
{
...
...
}
如果您调用 Task<TResult>.FromException()
或 Task.FromException()
,这两个调用没有区别。
您的方法签名是:public Task<User> GetAsync(string email)
现在,如果您尝试使用 Task<User>.FromException(new Exception())
,这将是 return Task<VoidTaskResult>
,这当然不是 Task<User>
类型。这意味着您可能遇到编译器错误。
如果您使用 Task<User>.FromException<User>(new Exception());
或 Task.FromException<User>(new Exception());
,此编译时错误将消失
您得到评论中指定的 Task<Task<VoidTaskResult>>
意味着您的方法代码中还有一些示例代码中未提及的内容。
有关 .Net 源代码中 Task
方法的更多内部详细信息,请参阅 here
更新:
查看您的代码后发现了几个问题。
i) Return type as Task 避免了我之前在回答中提到的编译错误。
ii) 您 return 在 Async 方法中使用 Task 而无需等待意味着完整的任务对象将包装在 Another Task 类型中。
请参阅下面我调整以显示问题的示例。看到即使 return 类型从对象类型的任务更改为程序,仍然没有错误。这是因为 Object 是 C# 中任何自定义类型的基础。所以允许执行以下操作:
static async Task<object> GetAsync()
{
try
{
throw new Exception();
}
catch (Exception e)
{
return Task.FromException<Program>(e);
}
}
现在将 return 类型的方法更改为 Task<Program>
您将收到错误或更多警告。
现在解决问题的正确版本是等待任务,这样只有程序类型保留为结果,它将自动 returned 为 Task<Program>
。
正确版本:
static async Task<Program> GetAsync()
{
try
{
throw new Exception();
}
catch (Exception e)
{
return await Task.FromException<Program>(e);
}
}
现在您将不会再看到任何嵌套任务。这是 await 工作原理的内部结构。如果你真的想知道为什么会这样,那么尝试使用 IL spy 分析所有上述 3 个版本的程序生成的 IL,你就会明白了。干杯!!!
Before/without Task<>
s,我正在使用如下所示的包装器对象来封装可能失败的 I/O 操作的结果,而不会将异常传播到调用堆栈:
public class FetchResult<T>
{
public readonly bool Success;
public readonly T Item;
public FetchResult(bool success, T item)
{
this.Success = success;
this.Item = item;
}
}
我会这样使用它:
var userResult = Get("robert.paulson@fightclub.com");
if(!userResult.Success)
// abort, or something
...
public FetchResult<User> Get(string email)
{
try
{
// go to database here, and get the User
return new FetchResult(true, new User());
}
catch
{
// log exception
return new FetchResult(false, null);
}
}
作为一个模型,这对我来说非常有用,因为它允许我在不使用 try/catch
作为程序控制流的情况下有效地管理异常,并为我提供了简单而细粒度的优雅服务降级。
然而,随着 Task<>
的出现,我很容易得到:
public Task<FetchResult<User>> GetAsync(string email)
这似乎失控了。
看到我到处都迁移到 async
,我正在考虑这样做:
public Task<User> GetAsync(string email)
哪个 id 允许我做类似的事情:
var userTask = GetAsync("robert.paulson@fightclub.com");
await userTask;
if(userTask.IsFaulted) // (*) - see below
// abort, or something
但是如果我的 GetAsync
方法 returns:
return Task<User>.FromException(new Exception());
在等待((*)
注释所在的位置)之后实际返回的内容似乎是已完成的 Task
,其结果是 Task<User>
,这是错误的。
为什么我在这种情况下得到了一个嵌套任务,我是否缺少一些语法糖来使整个事件更整洁?
Task.IsFaulted
表示that the Task completed due to an unhandled exception。所以我不认为你可以用不同的方式设置它而不是让异常未被捕获。
public async Task<User> GetAsync(string email)
{
// go to database here, and get the User
return new User();
}
或者捕获、记录并重新抛出:
public async Task<User> GetAsync(string email)
{
try
{
// go to database here, and get the User
return new User();
}
catch
{
// log exception
throw;
}
}
我相信你是说你的 GetAsync
是 return 嵌套任务,因为你有这样的事情:
public async Task GetAsync(string email)
{
// .... somewhere in the code
return Task.FromException(new Exception());
}
但是当您使用 async
关键字声明一个方法时,C# 魔法会负责将其内容包装在 Task
中。因此,在这种情况下,您实际上最终得到的任务 return 是另一项任务 - Task<Task<T>>
。如果删除 async
关键字,它将 return Task<T>
但显然你最终会使用标准同步方法。
有点不寻常,但您可以探索 Task.WhenAny method does not throw exception (see
var userTask = GetAsync("robert.paulson@fightclub.com");
await Task.WaitAny(userTask);
if(userTask.IsFaulted) // (*) - see below
// abort, or something
任务库中有两种方法 FromException()
和 FromException<TResult>()
都可以通过 Task
和 Task<TResult>
使用。
public static Task FromException(Exception exception)
{
return FromException<VoidTaskResult>(exception);
}
public static Task<TResult> FromException<TResult>(Exception exception)
{
...
...
}
如果您调用 Task<TResult>.FromException()
或 Task.FromException()
,这两个调用没有区别。
您的方法签名是:public Task<User> GetAsync(string email)
现在,如果您尝试使用 Task<User>.FromException(new Exception())
,这将是 return Task<VoidTaskResult>
,这当然不是 Task<User>
类型。这意味着您可能遇到编译器错误。
如果您使用 Task<User>.FromException<User>(new Exception());
或 Task.FromException<User>(new Exception());
您得到评论中指定的 Task<Task<VoidTaskResult>>
意味着您的方法代码中还有一些示例代码中未提及的内容。
有关 .Net 源代码中 Task
方法的更多内部详细信息,请参阅 here
更新:
查看您的代码后发现了几个问题。
i) Return type as Task 避免了我之前在回答中提到的编译错误。
ii) 您 return 在 Async 方法中使用 Task 而无需等待意味着完整的任务对象将包装在 Another Task 类型中。
请参阅下面我调整以显示问题的示例。看到即使 return 类型从对象类型的任务更改为程序,仍然没有错误。这是因为 Object 是 C# 中任何自定义类型的基础。所以允许执行以下操作:
static async Task<object> GetAsync()
{
try
{
throw new Exception();
}
catch (Exception e)
{
return Task.FromException<Program>(e);
}
}
现在将 return 类型的方法更改为 Task<Program>
您将收到错误或更多警告。
现在解决问题的正确版本是等待任务,这样只有程序类型保留为结果,它将自动 returned 为 Task<Program>
。
正确版本:
static async Task<Program> GetAsync()
{
try
{
throw new Exception();
}
catch (Exception e)
{
return await Task.FromException<Program>(e);
}
}
现在您将不会再看到任何嵌套任务。这是 await 工作原理的内部结构。如果你真的想知道为什么会这样,那么尝试使用 IL spy 分析所有上述 3 个版本的程序生成的 IL,你就会明白了。干杯!!!