背景
这篇文章算是从工作中学到的一点编码规范。我所在的组正在重构当前的系统,其中有一个部分是希望将所有异步的 IO 操作都包装起来,并提交给线程池统一管理。涉及到的接口简化后如下,可以看到,IExecutionEngine 的方法 GetResultAsync 包括参数上下文 IExecutionContext 和具体的 job IExecutionJob, 其中这个 job 是泛型的。方法 GetResultAsync 的返回结果是 Task<TResult>, 所以在它的实现里其实最终都是在调用 job.ExecuteAsync(),只是另外做了一些额外的公共操作。
public interface IExecutionEngine
{
Task<TResult> GetResultAsync<TResult>(IExecutionContext executionContext, IExecutionJob<TResult> job, bool throwIfFail);
}
public interface IExecutionJob<TResult>
{
public Task<TResult> ExecuteAsync(CancellationToken cancellationToken);
public Task<TResult> ExecuteAsync();
}那,要是我这个异步操作没有返回值怎么办呢?一个直观的解决方案就是再包一层,强行返回一个带 return type 的方法。比如下方的代码就是将 DoSomeThing() 再包装了一层,logic 的类型为 Func<Task<int>>。
var logic = async () =>
{
await DoSomeThing();
return 0;
};避免在异步中返回 void
那为什么这个接口设计一定需要返回值呢?需要注意的是,void 并不是一种泛型(就像 Action 和 Func<T> 的关系),所以如果要支持 void 的话,我们需要再另外提供一套接口。
public interface IExecutionEngine
{
Task GetResultAsync(IExecutionContext executionContext, IExecutionJob job, bool throwIfFail);
}
public interface IExecutionJob
{
public Task ExecuteAsync(CancellationToken cancellationToken);
public Task ExecuteAsync();
}这不仅仅不够优雅的问题,更深层次的原因是异步 void 方法可能在运行时导致一些问题,比如要是这个这个方法在其中抛出了异常,那么调用的线程并不会受到影响。
using System;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
RunThisAction(async () =>
{
await Task.Delay(1000);
throw new NotImplementedException();
});
}
public static String RunThisAction(Action doSomething)
{
doSomething();
return "OK";
}
}