从不同的请求中取消和创建任务
Cancelling and creating tasks from different requests
在一个 ASP.net 核心应用程序中,我在一个请求中生成后台任务,这些任务可以自行完成,也可以被第二个请求取消。我有以下实现,但我觉得应该有更好的方法来实现我想要的?有人对这个问题有经验吗?
public sealed class SomeService
{
private record BackgroundTask(Task Task,
CancellationTokenSource CancellationTokenSource);
private readonly ConcurrentDictionary<Guid, BackgroundTask> _backgroundTasks
= new();
public void Start(Guid id)
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var task = Task.Run(async () =>
{
try
{
await Task.Delay(1000, cancellationToken);
}
finally
{
_backgroundTasks.TryRemove(id, out _);
}
}, cancellationToken);
_backgroundTasks.TryAdd(id, new BackgroundTask(task, cancellationTokenSource));
}
public void Cancel(Guid id)
{
_backgroundTasks.TryGetValue(id, out var backgroundTask);
if (backgroundTask == null)
{
return;
}
backgroundTask.CancellationTokenSource.Cancel();
try
{
backgroundTask.Task.GetAwaiter().GetResult();
}
catch (OperationCanceledException e)
{
// todo: cancellation successful...
}
}
}
下面的代码中存在竞争条件:
var task = Task.Run(async () =>
{
try
{
await Task.Delay(1000, cancellationToken);
}
finally
{
_backgroundTasks.TryRemove(id, out _);
}
}, cancellationToken);
_backgroundTasks.TryAdd(id, new BackgroundTask(task, cancellationTokenSource));
理论上可能 task
完成后会被添加到 _backgroundTasks
中,因此它永远不会从字典中删除。
这个问题的一个解决方案是创建一个冷 Task
,并且 Start
只有在它被添加到字典中之后。您可以查看此技术的示例 here。使用冷任务是一项低级且棘手的技术,所以要小心!
另一种解决方案可能是根本不使用字典,而是通过引用而不是 id 来识别 BackgroundTask
。您不需要公开内部 BackgroundTask
类型。您可以 return 对其进行 object
引用,例如 所示。但我猜你有某种理由将 BackgroundTask
存储在一个集合中,以便你可以跟踪它们。
在一个 ASP.net 核心应用程序中,我在一个请求中生成后台任务,这些任务可以自行完成,也可以被第二个请求取消。我有以下实现,但我觉得应该有更好的方法来实现我想要的?有人对这个问题有经验吗?
public sealed class SomeService
{
private record BackgroundTask(Task Task,
CancellationTokenSource CancellationTokenSource);
private readonly ConcurrentDictionary<Guid, BackgroundTask> _backgroundTasks
= new();
public void Start(Guid id)
{
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
var task = Task.Run(async () =>
{
try
{
await Task.Delay(1000, cancellationToken);
}
finally
{
_backgroundTasks.TryRemove(id, out _);
}
}, cancellationToken);
_backgroundTasks.TryAdd(id, new BackgroundTask(task, cancellationTokenSource));
}
public void Cancel(Guid id)
{
_backgroundTasks.TryGetValue(id, out var backgroundTask);
if (backgroundTask == null)
{
return;
}
backgroundTask.CancellationTokenSource.Cancel();
try
{
backgroundTask.Task.GetAwaiter().GetResult();
}
catch (OperationCanceledException e)
{
// todo: cancellation successful...
}
}
}
下面的代码中存在竞争条件:
var task = Task.Run(async () =>
{
try
{
await Task.Delay(1000, cancellationToken);
}
finally
{
_backgroundTasks.TryRemove(id, out _);
}
}, cancellationToken);
_backgroundTasks.TryAdd(id, new BackgroundTask(task, cancellationTokenSource));
理论上可能 task
完成后会被添加到 _backgroundTasks
中,因此它永远不会从字典中删除。
这个问题的一个解决方案是创建一个冷 Task
,并且 Start
只有在它被添加到字典中之后。您可以查看此技术的示例 here。使用冷任务是一项低级且棘手的技术,所以要小心!
另一种解决方案可能是根本不使用字典,而是通过引用而不是 id 来识别 BackgroundTask
。您不需要公开内部 BackgroundTask
类型。您可以 return 对其进行 object
引用,例如 BackgroundTask
存储在一个集合中,以便你可以跟踪它们。