【发布时间】:2021-07-20 04:25:27
【问题描述】:
我没有使用传统的线程,而是使用async/await 来实现一个长时间运行的作业,该作业将从桌面/Web/移动等各种场景中调用。
这个问题是关于使用CancellationTokenSource/CancellationToken 对象时的设计注意事项。考虑以下用 .NET Core 5 编写的代码:
System
System.Collections.Generic
System.Diagnostics
System.IO
System.Threading
System.Threading.Tasks
[STAThread]
private static async Task Main ()
{
using (var job = new Job())
//using (var source = new CancellationTokenSource())
{
var watch = Stopwatch.StartNew();
job.OnJobProgress += (sender, e) => { Console.WriteLine (watch.Elapsed); };
Task.Run (async () => await job.StartAsync());
//Task.Run (async () => await job.StartAsync (source.Token));
do
{
await Task.Delay (100);
if ((Console.KeyAvailable) && (Console.ReadKey ().Key == ConsoleKey.Escape))
{
//source.Cancel();
await job.CancelAsync();
break;
}
}
while (job.Running);
}
}
public class Job : IDisposable
{
public EventHandler OnJobProgress;
private bool _Running = false;
private readonly object SyncRoot = new object();
private CancellationTokenSource CancellationTokenSource = new CancellationTokenSource();
public bool Running => this._Running;
public async Task StartAsync () => await this.StartAsync(CancellationToken.None);
public async Task StartAsync (CancellationToken cancellationToken) => await this.ProcessAsync(cancellationToken);
public void Cancel ()
{
this.CancellationTokenSource?.Cancel();
do { Thread.Sleep (10); } while (this._Running);
}
public async Task CancelAsync ()
{
this.CancellationTokenSource?.Cancel();
do { await Task.Delay (10); } while (this._Running);
}
private async Task ProcessAsync (CancellationToken cancellationToken)
{
lock (this.SyncRoot)
{
if (this._Running) { return; }
else { this._Running = true; }
}
do
{
await Task.Delay (100);
this.OnJobProgress?.Invoke (this, new EventArgs());
}
while (!cancellationToken.IsCancellationRequested);
lock (this.SyncRoot)
{
this._Running = false;
this.CancellationTokenSource?.Dispose();
this.CancellationTokenSource = new CancellationTokenSource();
}
}
public void Dispose () => this.Cancel();
}
注意Main 方法以及Cancel 和CancelAsync 方法中的三个注释行。我的直觉说应该在Cancel 方法而不是Process 方法中有一个锁定机制。根据CancellationToken 的来源,此实现中是否存在任何潜在的死锁?不知何故,我对do/while 阻塞机制感到不舒服。
任何想法都将不胜感激。
辅助问题:既然CancellationToken 是一个readonly struct 并被传递给by value,那么在CancellationTokenSource 上调用Cancel 是如何修改CancellationToken.IsCancellationRequested 属性的?也许这一直是混乱的根源。
【问题讨论】:
-
Somehow, I am not comfortable with the do/while blocking mechanism- 所以不要使用它?只需在Main中await你的Task.Run(...),然后有一个不相关的按键处理程序来标记取消令牌? -
你的直觉是正确的。
bool标志和观察该标志的循环的这种组合效率低下,引入了延迟,而且肯定有问题。一个线程不应该在没有同步的情况下读取由另一个线程变异的非volatile变量。当前线程可能看不到更改的值。你可以看看这个:C# and thread-safety of a bool.
标签: c# .net .net-core async-await cancellationtokensource