如何解决如何确定 CancellationTokenSource 范围?
我没有使用传统的线程,而是使用 async/await
来实现一个长时间运行的作业,该作业将从桌面/网络/移动等各种场景中调用。
这个问题是关于使用 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
周围传递,所以如何在 {{ 1}} 修改了 Cancel
属性?也许这就是一直困惑的根源。
解决方法
这是 Task.WhenAny 的工作。等待第一个工作从两个完成:您真正想要完成的工作或通过点击 ESC 键或适当的移动触摸来表示用户不耐烦的工作。
伪代码:
-
mainTask = Setup main task,take the token as input
。就是这样。 -
userInterruptTask = Setup user action monitoring task
,在它的继续或作为其自然循环结束时间的一部分(ESC 键),调用 Cancel。注意,在这个循环中,没有对布尔值进行检查;它一直持续到必须取消,然后通过中断/返回完成;如果其他任务正确侦听取消,则会完成。 - 因此,当任一任务完成时,您就完成了。
var ret = await Task.WhenAny(mainTask,userInterruptTask);
如果此时重要,请获取 ret
的值并采取相应措施。 Task.WhenAny returns
表示完成提供的任务之一的任务。返回任务的 Result 是完成的任务。
对于令牌的“范围是什么”的具体答案......它的范围是所有可能对它起作用的东西。 TPL 中的取消是 100% 合作的,因此所有关心设置取消或寻找取消的任务都在起作用。
对于你的辅助问题,我能理解你的困惑。我自己以前没有想过,但答案很简单。该属性的实现委托给令牌源:
public bool IsCancellationRequested
=> _source != null && _source.IsCancellationRequested;
其中 CancellationTokenSource 是一个有状态的类。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。