如何确定 CancellationTokenSource 范围?
How to determine CancellationTokenSource scope?
我没有使用常规线程,而是使用 async/await
来实现一个长 运行 作业,该作业将从 Desktop/Web/Mobile.
等各种场景调用
这个问题是关于使用 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
周围传递,所以调用 Cancel
在CancellationTokenSource
上修改CancellationToken.IsCancellationRequested
属性?也许这就是一直以来混乱的根源。
这是 Task.WhenAny 的工作。等待第一项工作从两项中完成:要么是您真正想要完成的工作,要么是代表用户不耐烦的工作,请按 ESC 键或适当的移动触摸。
伪代码:
mainTask = Setup main task, take the token as input
。就是这样。
userInterruptTask = Setup user action monitoring task
,并在它的延续中或作为其自然循环结束时间的一部分(ESC 键),调用 Cancel。请注意,在此循环中,没有针对布尔值的检查;它一直持续到它必须取消,然后通过 break/return 完成;如果正确侦听取消,则其他任务将完成。
- 因此,当任一任务完成时,您就完成了。
var ret = await Task.WhenAny(mainTask, userInterruptTask);
如果此时重要,请获取 ret
的值并采取相应的行动。 Task.WhenAnyreturns
A task that represents the completion of one of the supplied tasks. The return task's Result is the task that completed.
对于令牌“范围是什么”的具体答案...它的范围是可能作用于它的一切。 TPL 中的取消是 100% 合作的,因此所有关心设置取消或寻找取消的任务都在进行中。
对于你的辅助问题,我能理解你的困惑。我自己以前没有想到过,但事实证明答案很简单。 属性 委托给令牌源的实现:
public bool IsCancellationRequested
=> _source != null && _source.IsCancellationRequested;
其中 CancellationTokenSource 是有状态的 class。
我没有使用常规线程,而是使用 async/await
来实现一个长 运行 作业,该作业将从 Desktop/Web/Mobile.
这个问题是关于使用 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
周围传递,所以调用 Cancel
在CancellationTokenSource
上修改CancellationToken.IsCancellationRequested
属性?也许这就是一直以来混乱的根源。
这是 Task.WhenAny 的工作。等待第一项工作从两项中完成:要么是您真正想要完成的工作,要么是代表用户不耐烦的工作,请按 ESC 键或适当的移动触摸。
伪代码:
mainTask = Setup main task, take the token as input
。就是这样。userInterruptTask = Setup user action monitoring task
,并在它的延续中或作为其自然循环结束时间的一部分(ESC 键),调用 Cancel。请注意,在此循环中,没有针对布尔值的检查;它一直持续到它必须取消,然后通过 break/return 完成;如果正确侦听取消,则其他任务将完成。- 因此,当任一任务完成时,您就完成了。
var ret = await Task.WhenAny(mainTask, userInterruptTask);
如果此时重要,请获取 ret
的值并采取相应的行动。 Task.WhenAnyreturns
A task that represents the completion of one of the supplied tasks. The return task's Result is the task that completed.
对于令牌“范围是什么”的具体答案...它的范围是可能作用于它的一切。 TPL 中的取消是 100% 合作的,因此所有关心设置取消或寻找取消的任务都在进行中。
对于你的辅助问题,我能理解你的困惑。我自己以前没有想到过,但事实证明答案很简单。 属性 委托给令牌源的实现:
public bool IsCancellationRequested
=> _source != null && _source.IsCancellationRequested;
其中 CancellationTokenSource 是有状态的 class。