TAP 模式指南关于如何处理计算绑定方法有个细节,“如果操作是纯计算绑定的,那么它应该仅作为同步实现公开,然后由方法的使用者决定是否将该同步方法的调用包装到 Task 中,进而使用多线程或实现并行”。
计算绑定指持续耗费大量 CPU 时间的操作
换句话说,如果你是编写代码库给他人使用,那就不要试着去预测别人会怎么用,直接暴露同步方法就行了。
创建同步的计算绑定方法
该方法通过调用 MineAsyncCoinsWithNthRoot 模拟计算绑定。
public MiningResultDto RentTimeOnLocalMiningServer(string authToken, int requestedIterations){if (!AuthorizeTheToken(authToken)){throw new Exception("Failed Authorization");}var result = new MiningResultDto();var startTime = DateTime.UtcNow;var coinAmount = MineAsyncCoinsWithNthRoot(requestedIterations);result.ElapsedSeconds = (DateTime.UtcNow - startTime).TotalSeconds;result.MiningText = $"You've got {coinAmount:N} AsyncCoin!";return result;}
MineAsyncCoinsWithNthRoot:
通过循环模拟 CPU 耗时操作(即计算绑定操作)。
private double MineAsyncCoinsWithNthRoot(int iterationMultiplier){double allCoins = 0;for (int i = 1; i < iterationMultiplier * 2500; i++){for (int j = 0; j <= i; j++){Math.Pow(i, 1.0 / j);allCoins += .000001;}}return allCoins;}
MiningResultDto:
创建该类只是为了通过它返回多个数据。
namespace TapPatterns{public class MiningResultDto{public string MiningText { get; set; }public double ElapsedSeconds { get; set; }}}
调用同步的计算绑定方法
public void Launch(){LaunchMiningMethodLocalWithTasks();}public void LaunchMiningMethodLocalWithTasks(){var localMiningTaskList = new List<Task<MiningResultDto>>();for (int i = 0; i < 3; i++){Task<MiningResultDto> task = Task.Run(() => RentTimeOnLocalMiningServer("SecretToken", 4));localMiningTaskList.Add(task);}var localMiningArray = localMiningTaskList.ToArray();Task.WaitAll(localMiningArray);foreach (var task in localMiningArray){Console.WriteLine($"mining result: {task.Result.MiningText}");Console.WriteLine($"Elapsed seconds: {task.Result.ElapsedSeconds:N}");}}
在 Main 方法中进行调用:
static void Main(string[] args){Console.WriteLine("start");var manager = new TapPatternManager();manager.Launch();Console.ReadLine();}
运行结果:
start
mining result: You’ve got 5.00 AsyncCoin!
Elapsed seconds: 3.15
mining result: You’ve got 5.00 AsyncCoin!
Elapsed seconds: 3.27
mining result: You’ve got 5.00 AsyncCoin!
Elapsed seconds: 2.89
这就是代码如果会被他人使用时,对计算绑定方法的处理 —— 保持方法为同步方法。
将计算绑定方法包装成 Task
下面演示如何通过 TaskCompletionSource 将上面的同步计算绑定方法包装成一个 Task。
public Task<MiningResultDto> RentTimeOnLocalMiningServerTask(string authToken, int requestedIterations){if (!AuthorizeTheToken(authToken)){throw new Exception("Failed Authorization");}var tcs = new TaskCompletionSource<MiningResultDto>();var result = new MiningResultDto();var startTime = DateTime.UtcNow;var localMiningTaskList = new List<Task<MiningResultDto>>();for (int i = 0; i < 3; i++){Task<MiningResultDto> task = Task.Run(() => RentTimeOnLocalMiningServer("SecretToken", 4));localMiningTaskList.Add(task);}var localMiningArray = localMiningTaskList.ToArray();Task.WaitAll(localMiningArray);foreach (var task in localMiningArray){result.MiningText += task.Result.MiningText + Environment.NewLine;}result.ElapsedSeconds = (DateTime.UtcNow - startTime).TotalSeconds;tcs.SetResult(result);return tcs.Task;}
调用计算绑定的 Task
public async Task LaunchAsync(){await LaunchMiningMethodLocalAsync();}public async Task LaunchMiningMethodLocalAsync(){MiningResultDto result = await RentTimeOnLocalMiningServerTask("SecretToken", 5);Console.WriteLine($"mining result: {result.MiningText}");Console.WriteLine($"Elapsed seconds: {result.ElapsedSeconds:N}");}
在 Main 方法中进行调用:
static async Task Main(string[] args){Console.WriteLine("start");var manager = new TapPatternManager();await manager.LaunchAsync();Console.ReadLine();}
运行结果:
start
mining result: You’ve got 5.00 AsyncCoin!
You’ve got 5.00 AsyncCoin!
You’ve got 5.00 AsyncCoin!
Elapsed seconds: 5.16
总结
使用 TAP 模式处理计算绑定操作的两种方案:
如果代码会被他人使用,那最好保持方法为同步方法
如果方法是写来你自己用的,封不封装为 Task 都行
本例还讲解了如何使用 TaskCompletionSource 手动创建 Task
详细代码见 GitHub。
