TAP 模式指南关于如何处理计算绑定方法有个细节,“如果操作是纯计算绑定的,那么它应该仅作为同步实现公开,然后由方法的使用者决定是否将该同步方法的调用包装到 Task 中,进而使用多线程或实现并行”。

计算绑定指持续耗费大量 CPU 时间的操作

换句话说,如果你是编写代码库给他人使用,那就不要试着去预测别人会怎么用,直接暴露同步方法就行了。

创建同步的计算绑定方法

该方法通过调用 MineAsyncCoinsWithNthRoot 模拟计算绑定。

  1. public MiningResultDto RentTimeOnLocalMiningServer(string authToken, int requestedIterations)
  2. {
  3. if (!AuthorizeTheToken(authToken))
  4. {
  5. throw new Exception("Failed Authorization");
  6. }
  7. var result = new MiningResultDto();
  8. var startTime = DateTime.UtcNow;
  9. var coinAmount = MineAsyncCoinsWithNthRoot(requestedIterations);
  10. result.ElapsedSeconds = (DateTime.UtcNow - startTime).TotalSeconds;
  11. result.MiningText = $"You've got {coinAmount:N} AsyncCoin!";
  12. return result;
  13. }

MineAsyncCoinsWithNthRoot:
通过循环模拟 CPU 耗时操作(即计算绑定操作)。

  1. private double MineAsyncCoinsWithNthRoot(int iterationMultiplier)
  2. {
  3. double allCoins = 0;
  4. for (int i = 1; i < iterationMultiplier * 2500; i++)
  5. {
  6. for (int j = 0; j <= i; j++)
  7. {
  8. Math.Pow(i, 1.0 / j);
  9. allCoins += .000001;
  10. }
  11. }
  12. return allCoins;
  13. }

MiningResultDto:
创建该类只是为了通过它返回多个数据。

  1. namespace TapPatterns
  2. {
  3. public class MiningResultDto
  4. {
  5. public string MiningText { get; set; }
  6. public double ElapsedSeconds { get; set; }
  7. }
  8. }

调用同步的计算绑定方法

  1. public void Launch()
  2. {
  3. LaunchMiningMethodLocalWithTasks();
  4. }
  5. public void LaunchMiningMethodLocalWithTasks()
  6. {
  7. var localMiningTaskList = new List<Task<MiningResultDto>>();
  8. for (int i = 0; i < 3; i++)
  9. {
  10. Task<MiningResultDto> task = Task.Run(() => RentTimeOnLocalMiningServer("SecretToken", 4));
  11. localMiningTaskList.Add(task);
  12. }
  13. var localMiningArray = localMiningTaskList.ToArray();
  14. Task.WaitAll(localMiningArray);
  15. foreach (var task in localMiningArray)
  16. {
  17. Console.WriteLine($"mining result: {task.Result.MiningText}");
  18. Console.WriteLine($"Elapsed seconds: {task.Result.ElapsedSeconds:N}");
  19. }
  20. }

在 Main 方法中进行调用:

  1. static void Main(string[] args)
  2. {
  3. Console.WriteLine("start");
  4. var manager = new TapPatternManager();
  5. manager.Launch();
  6. Console.ReadLine();
  7. }

运行结果:
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。

  1. public Task<MiningResultDto> RentTimeOnLocalMiningServerTask(string authToken, int requestedIterations)
  2. {
  3. if (!AuthorizeTheToken(authToken))
  4. {
  5. throw new Exception("Failed Authorization");
  6. }
  7. var tcs = new TaskCompletionSource<MiningResultDto>();
  8. var result = new MiningResultDto();
  9. var startTime = DateTime.UtcNow;
  10. var localMiningTaskList = new List<Task<MiningResultDto>>();
  11. for (int i = 0; i < 3; i++)
  12. {
  13. Task<MiningResultDto> task = Task.Run(() => RentTimeOnLocalMiningServer("SecretToken", 4));
  14. localMiningTaskList.Add(task);
  15. }
  16. var localMiningArray = localMiningTaskList.ToArray();
  17. Task.WaitAll(localMiningArray);
  18. foreach (var task in localMiningArray)
  19. {
  20. result.MiningText += task.Result.MiningText + Environment.NewLine;
  21. }
  22. result.ElapsedSeconds = (DateTime.UtcNow - startTime).TotalSeconds;
  23. tcs.SetResult(result);
  24. return tcs.Task;
  25. }

调用计算绑定的 Task

  1. public async Task LaunchAsync()
  2. {
  3. await LaunchMiningMethodLocalAsync();
  4. }
  5. public async Task LaunchMiningMethodLocalAsync()
  6. {
  7. MiningResultDto result = await RentTimeOnLocalMiningServerTask("SecretToken", 5);
  8. Console.WriteLine($"mining result: {result.MiningText}");
  9. Console.WriteLine($"Elapsed seconds: {result.ElapsedSeconds:N}");
  10. }

在 Main 方法中进行调用:

  1. static async Task Main(string[] args)
  2. {
  3. Console.WriteLine("start");
  4. var manager = new TapPatternManager();
  5. await manager.LaunchAsync();
  6. Console.ReadLine();
  7. }

运行结果:
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