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。