如何实现返回Task<;T>;
本文关键字:lt gt Task 返回 何实现 实现 | 更新日期: 2023-09-27 18:29:21
我有一个接口
interface IFoo
{
Task<Bar> CreateBarAsync();
}
创建Bar
有两种方法,一种是异步方法,另一种是同步方法。我想为这两种方法中的每一种提供一个接口实现。
对于异步方法,实现可能如下所示:
class Foo1 : IFoo
{
async Task<Bar> CreateBarAsync()
{
return await AsynchronousBarCreatorAsync();
}
}
但是,我应该如何实现使用同步方法创建Bar
的类Foo2
?
I可以实现同步运行的方法:
async Task<Bar> CreateBarAsync()
{
return SynchronousBarCreator();
}
然后编译器将警告不要在方法签名中使用async
:
此异步方法缺少"等待"运算符,将同步运行。请考虑使用'await'运算符等待非阻塞API调用,或使用'await Task.Run(…)'在后台线程上执行CPUbound工作。
或者,I可以实现显式返回Task<Bar>
的方法。在我看来,代码的可读性会降低:
Task<Bar> CreateBarAsync()
{
return Task.Run(() => SynchronousBarCreator());
}
从性能的角度来看,我认为这两种方法的开销大致相同,或者?
我应该选择哪种方法;同步实现async
方法或者显式地将同步方法调用封装在Task
中?
编辑
我正在处理的项目实际上是一个.NET 4项目,具有来自Microsoft async NuGet包的async/await扩展。在.NET 4上,Task.Run
可以替换为TaskEx.Run
。我有意识地在上面的例子中使用了.NET 4.5方法,希望能使主要问题更加清楚。
当您必须从接口实现异步方法并且您的实现是同步的时,您可以使用Ned的解决方案:
public Task<Bar> CreateBarAsync()
{
return Task.FromResult<Bar>(SynchronousBarCreator());
}
使用此解决方案,该方法看起来是异步的,但却是同步的。
或者你提出的解决方案:
Task<Bar> CreateBarAsync()
{
return Task.Run(() => SynchronousBarCreator());
}
通过这种方式,该方法是真正异步的。
您没有一个通用的解决方案来匹配"如何实现返回Task的接口方法"的所有情况。这取决于上下文:您的实现是否足够快,因此在另一个线程上调用它是无用的?这个接口是如何使用的?当调用这个方法时(它会冻结应用程序吗)?是否可以在另一个线程中调用您的实现?
试试这个:
class Foo2 : IFoo
{
public Task<Bar> CreateBarAsync()
{
return Task.FromResult<Bar>(SynchronousBarCreator());
}
}
Task.FromResult
使用提供的值创建指定类型的已完成任务。
如果您使用的是.NET 4.0,则可以使用TaskCompletionSource<T>
:
Task<Bar> CreateBarAsync()
{
var tcs = new TaskCompletionSource<Bar>();
tcs.SetResult(SynchronousBarCreator());
return tcs.Task
}
最终,如果您的方法没有任何异步内容,那么您应该考虑公开一个同步端点(CreateBar
),它将创建一个新的Bar
。这样就不会有任何意外,也不需要使用冗余的Task
进行包装。
为了补充其他答案,还有一个选项,我相信它也适用于.NET 4.0:
class Foo2 : IFoo
{
public Task<Bar> CreateBarAsync()
{
var task = new Task<Bar>(() => SynchronousBarCreator());
task.RunSynchronously();
return task;
}
}
注task.RunSynchronously()
。与Task<>.FromResult
和TaskCompletionSource<>.SetResult
相比,可能是最慢的选项,但有一个微妙但重要的区别:错误传播行为。
上面的方法将模仿async
方法的行为,其中异常从不在同一堆栈帧上抛出(展开它),而是存储在Task
对象中处于休眠状态。调用方实际上必须通过await task
或task.Result
来观察它,此时它将被重新抛出。
Task<>.FromResult
和TaskCompletionSource<>.SetResult
的情况并非如此,SynchronousBarCreator
引发的任何异常都将直接传播到调用者,从而展开调用堆栈。
我在这里有一个更详细的解释:
";wait Task.Run();回归"以及";return Task.Run()"?
附带说明一下,我建议在设计接口时添加取消的条款(即使当前没有使用/实施取消):
interface IFoo
{
Task<Bar> CreateBarAsync(CancellationToken token);
}
class Foo2 : IFoo
{
public Task<Bar> CreateBarAsync(CancellationToken token)
{
var task = new Task<Bar>(() => SynchronousBarCreator(), token);
task.RunSynchronously();
return task;
}
}