异步编程基础——返回已完成的任务
问题
通过异步签名实现同步方法,如果在继承异步接口或基本类的同时又想同步实现该任务,变可能发生这种情况。当需要异步接口的简单存根或模拟对象时,或者当需要对异步代码左单元测试时,这项技术特别有用。
解决方案
使用Task.FromResult来创建并返回新的Task<T>,这个Task<T>已通过指定的值完成。
interface IMyAsyncInterface
{
Task<int> GetValueAsync();
}
class MySynchronousImplementation:IMyAsyncInterface
{
public Task<int> GetValueAsync()
{
return Task.FromResult(13);
}
}
对于无返回值的方法,可以使用Task.CompletedTask,这是一个已经成功完成的缓存任务;
interface IMyAsyncInterface
{
Task DoSomethingAsync();
}
class MySynchronousImplementation:IMyAsyncInterface
{
public Task DoSomethingAsync()
{
return Task.CompletedTask;
}
}
Task.FromResult只为成功的结果提供已完成的任务。若需要具有不同结果的任务(比如通过NotImplementedException完成任务),则可以使用Task.FromException:
Task<T> NotImplementedAsync<T>()
{
return Task.FromException<T>(new NotImplementedException());
}
类似地,已经从给定的CancellationToken中取消任务,可以使用Task.FromCanceled创建:
Task<int> GetValueAsync(CancellationToken cancellationToken)
{
if(cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<int>(cancellationToken);
}
return Task.FromResult(13);
}
如果同步实现可能会失败,就应当捕捉异常并通过Task.FromException来返回它们:
interface IMyAsyncInterface
{
Task DoSomethingAsync();
}
class MySynchronousImplementation:IMyAsyncInterface
{
public Task DoSomethingAsync()
{
try
{
DoSomethingSynchronously();
return Task.CompletedTask;
}
catch(Exception ex)
{
return Task.FromException(ex);
}
}
}
讨论
若要通过同步代码来实现异步接口,需要避免任何形式的阻塞。当方法可以异步实现时,阻塞异步方法然后返回已完成的任务,这种情况很不理想
如果经常以同一个值使用Task.FromResult,那么可以考虑缓存实际任务。比如,若以0为结果创建一次Task<int>,就避免了创建更多必须予以垃圾回收的实例化对象。
private static readonly Task<int> zeroTask=Task.FromResult(0);
Task<int> GetValueAsync()
{
return zeroTask;
}
从逻辑上来讲,Task.FromResult、Task.FromException和Task.FromCanceled都是辅助方法,也都是通用的TaskCompletionSource<T>的快捷方式。通常来说,若要返回已完成的任务,应当使用Task.FromResult、Task.FromException和Task.FromCanceled。使用TaskCompletionSource<T>可以返回在未来某个时刻完成的任务