3.更容易的处理线程安全问题。
private int _count = 0; // 堆代码 duidaima.com private void ProcessTest(Action<CancellationToken> action, [CallerMemberName] string methodName = "") { var cts = new CancellationTokenSource(); // 启动常驻线程 action.Invoke(cts.Token); // 严架给压力 YanjiaIsComing(cts.Token); // 等待一段时间 Thread.Sleep(TimeSpan.FromSeconds(5)); cts.Cancel(); // 输出结果 Console.WriteLine($"{methodName}: count = {_count}"); } private void YanjiaIsComing(CancellationToken token) { Parallel.ForEachAsync(Enumerable.Range(0, 1_000_000), token, (i, c) => { while (true) { // do something c.ThrowIfCancellationRequested(); } }); }这里我们定义了一个 ProcessTest 方法,用于评测常驻任务的正确性。我们将在这个方法中启动常驻任务,然后执行一个严架给压力的方法,来模拟非常繁忙的业务操作。最后我们将输出常驻任务中的计数器的值。
[Test] public void TestTaskRun_Error() { ProcessTest(token => { Task.Run(async () => { while (true) { _count++; await Task.Delay(TimeSpan.FromSeconds(1), token); } }, token); }); // TestTaskRun_Error: count = 1 }在该测试中,我们希望使用 Task.Run 来执行我们期待的循环,进行每秒加一的操作。但是,我们发现,最终输出的结果是 1。这是因为:
[Test] public void TestSyncTaskLongRunning_Success() { ProcessTest(token => { Task.Factory.StartNew(() => { while (true) { _count++; Thread.Sleep(TimeSpan.FromSeconds(1)); } }, token, TaskCreationOptions.LongRunning, TaskScheduler.Current); }); // TestSyncTaskLongRunning_Success: count = 6 } [Test] public void TestThread_Success() { ProcessTest(token => { new Thread(() => { while (true) { _count++; Thread.Sleep(TimeSpan.FromSeconds(1)); if (token.IsCancellationRequested) { return; } } }) { IsBackground = true, }.Start(); }); // TestThread_Success: count = 6 }这两种正确的写法都实现了常驻单一线程,因此我们可以看到,最终输出的结果都是 6。
[Test] public void TestAsyncTaskLongRunning_Error() { ProcessTest(token => { Task.Factory.StartNew(async () => { while (true) { _count++; await Task.Delay(TimeSpan.FromSeconds(1), token); } }, token, TaskCreationOptions.LongRunning, TaskScheduler.Current); }); // TestAsyncTaskLongRunning_Error: count = 1 } [Test] public void TestThreadWithAsync_Error() { ProcessTest(token => { Task CountUp(CancellationToken c) { _count++; return Task.CompletedTask; } new Thread(async () => { while (true) { try { await CountUp(token); await Task.Delay(TimeSpan.FromSeconds(1), token); token.ThrowIfCancellationRequested(); } catch (OperationCanceledException e) { return; } } }) { IsBackground = true, }.Start(); }); // TestThreadWithAsync_Error: count = 1 }这两种错误的写法都无法实现常驻单一线程,因此我们可以看到,最终输出的结果都是 1。
[Test] public void TestThreadWithTask_Success() { // 堆代码 duidaima.com ProcessTest(token => { Task CountUp(CancellationToken c) { _count++; return Task.CompletedTask; } new Thread(() => { while (true) { try { CountUp(token).Wait(token); Thread.Sleep(TimeSpan.FromSeconds(1)); } catch (OperationCanceledException e) { return; } } }) { IsBackground = true, }.Start(); }); // TestThreadWithTask_Success: count = 6 } [Test] public void TestThreadWithDelayTask_Error() { ProcessTest(token => { Task CountUp(CancellationToken c) { _count++; return Task.Delay(TimeSpan.FromSeconds(1), c); } new Thread(() => { while (true) { try { CountUp(token).Wait(token); token.ThrowIfCancellationRequested(); } catch (OperationCanceledException e) { return; } } }) { IsBackground = true, }.Start(); }); // TestThreadWithDelayTask_Error: count = 1 }在这两个 case 但中,虽然在 while 中包含了 wait Task,但是由于 Task.CompletedTask 实际上是一种同步代码,所以并不会进入到线程池当中。因此也就不会出现错误的情况。