英文:
Trivial exit from a method that may perform an asynchronous call
问题
我遇到了一个问题,调用一个方法,该方法可能根据初始同步查找调用的结果返回一个 Task<T>
或 null
(这本身可能是一个反模式,请告诉我)。我想在发生微不足道的退出条件时返回 null
,但这会导致外部调用方法失败,因为外部调用期望一个 Task<T>
响应(微不足道的退出条件),该响应通过 ConfigureAwait(true)
传递,随后产生 NullReferenceException
。
外部调用方法:
var res = await MyService.GetUserCourseStatusAsync(userID, productID).ConfigureAwait(true);
中间方法:
public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// 查找用户的ID(如果存在)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return null; // 微不足道的返回null("用户未注册")。 *** 这会导致上面的ConfigureAwait(true)抛出异常 ***
// 另一个同步调用
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"产品 #{productID} 不是课程");
// 对内部方法进行异步调用(这部分工作正常)
return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
因此,我的想法是我们应该始终返回 Task<T>
而不是 null
。
然而,所有这些都会引发编译错误:
//return null; // 微不足道的返回null("用户未注册")。 *** 这会导致异常
// 编译错误:CS0029:无法将类型'GetUserCourseInner'隐式转换为'System.Threading.Tasks.Task<IGetUserCourseResponse>'
return new GetUserCourseInner(); // 未注册
// 编译错误:CS1503 参数1:无法将类型'GetUserCourseInner'转换为'System.Func<IGetUserCourseResponse>'
return new Task<IGetUserCourseResponse>(new GetUserCourseInner()); // 未注册
我该如何返回一个不是异步调用结果的虚拟 Task<T>
?
这甚至是正确的方法吗?
英文:
I've got an issue calling a method that may return a Task<T>
or null
depending on the result of an initial synchronous lookup call (this itself might be an anti-pattern so please let me know).
I kind of want to return null
if a trivial exit condition occurs but this is causing the calling (outer) method to fail because the outer call expects a Task<T>
response (trivial exit) which gets pushed through to ConfigureAwait(true)
which subsequently produces a NullReferenceException
.
Outer calling method:
var res = await MyService.GetUserCourseStatusAsync(userID, productID).ConfigureAwait(true);
Intermediate method:
public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return null; // Trivial return of null ("User not registered"). *** This causes exception on ConfigureAwait(true) above ***
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
So my thought then is that we should always return a Task<T>
instead of null
.
However, all of these cause compile errors:
//return null; // Trivial return of null ("User not registered"). *** This causes exception
// Compile error: CS0029: Cannot implicitly convert type 'GetUserCourseInner' to 'System.Threading.Tasks.Task<IGetUserCourseResponse>'
return new GetUserCourseInner(); // Not registered
// Compile error: CS1503 Argument 1: cannot convert from 'GetUserCourseInner' to 'System.Func<IGetUserCourseResponse>'
return new Task<IGetUserCourseResponse>(new GetUserCourseInner()); // Not registered
How do I return a dummy Task<T>
that isn't a result of a async call?
Is this even the correct approach?
答案1
得分: 4
以下是您要翻译的内容:
原文:
It would be better, as you suggested, to return a Task<IGetUserCourseResponse>
which contains null
(or some other sentinel value). You can create such a completed Task
with Task.FromResult((IGetUserCourseResponse)null)
:
翻译:
正如您建议的那样,最好返回一个包含 null
(或其他标记值)的 Task<IGetUserCourseResponse>
。您可以使用 Task.FromResult((IGetUserCourseResponse)null)
创建这样一个已完成的 Task
:
原文:
public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return Task.FromResult((IGetUserCourseResponse)null);
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
翻译:
public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// 查找用户的ID(如果存在)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return Task.FromResult((IGetUserCourseResponse)null);
// 另一个同步调用
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// 对内部方法进行异步调用(这部分工作正常)
return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
原文:
Alternatively, you could make your outer method async
. Note however that this changes its behaviour in the case where it throws an InvalidOperationException
: instead of the method throwing this exception directly, it will instead return a Task
which contains this exception. This may or may not be what you want:
翻译:
或者,您可以将外部方法标记为 async
。请注意,在抛出 InvalidOperationException
的情况下,这将更改其行为:而不是方法直接抛出此异常,而是返回包含此异常的 Task
。这可能或可能不是您想要的:
原文:
public async Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return null;
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return await GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
翻译:
public async Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// 查找用户的ID(如果存在)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return null;
// 另一个同步调用
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// 对内部方法进行异步调用(这部分工作正常)
return await GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
英文:
It would be better, as you suggested, to return a Task<IGetUserCourseResponse>
which contains null
(or some other sentinel value). You can create such a completed Task
with Task.FromResult((IGetUserCourseResponse)null)
:
public Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return Task.FromResult((IGetUserCourseResponse)null);
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
Alternatively, you could make your outer method async
. Note however that this changes its behaviour in the case where it throws an InvalidOperationException
: instead of the method throwing this exception directly, it will instead return a Task
which contains this exception. This may or may not be what you want:
public async Task<IGetUserCourseResponse> GetUserCourseStatusAsync(int userID, int productID)
{
// Look up User's ID (if exists)
var userCredentials = GetUserCredentials(userID);
if (userCredentials?.UserID == null)
return null;
// Another synchronous call
var courseId = GetCourseID(productID);
if (courseId == null)
throw new InvalidOperationException($"Product #{productID} is not a course");
// Asynchronous call to inner method (this bit works fine)
return await GetUserCourseAsync(userCredentials.UserID.Value, courseId.Value);
}
答案2
得分: 3
返回一个包含空值作为结果的任务。
return Task.FromResult<IGetUserCourseResponse>(null);
英文:
Just return a Task that holds a null value as result
return Task.FromResult<IGetUserCourseResponse>(null);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论