提问人:Thimo Luijsterburg 提问时间:7/27/2023 最后编辑:Thimo Luijsterburg 更新时间:7/27/2023 访问量:47
如何使用 cancellationTokens 释放 LongRunningTask
How to dispose a LongRunningTask using cancellationTokens
问:
与这个问题相关:我需要取消 signalR 中的长时间运行循环,我该怎么做?
我设置了一种快速的方法来更好地了解自己的情况,并找到了半个解决方案。我能够取消任务(不确定如何取消,因为 CancellationTokenSource 在 .Cancel() 但无论如何)。但似乎有一个新问题。这是我当前的测试代码:
服务器端集线器:
public class MyHub : Hub<IMyHub>
{
private CancellationTokenSource cancellationTokenSource;
public void StartLongRunningTask()
{
cancellationTokenSource = new CancellationTokenSource();
try
{
LongRunningTask(cancellationTokenSource.Token);
// Task completed successfully
}
catch (TaskCanceledException)
{
// Task was canceled by the client
// Handle cancellation logic here
}
finally
{
cancellationTokenSource.Dispose();
}
}
public async void CancelLongRunningTask()
{
cancellationTokenSource?.Cancel();
await Clients.All.SendStatus(false);
}
private void LongRunningTask(CancellationToken ct)
{
// Your long-running task implementation
// Ensure to check the cancellation token periodically and stop processing if canceled
while (!ct.IsCancellationRequested)
{
_ = Clients.Caller.SendStatus(true);
_ = Task.Delay(1000, ct);
// Continue with other work...
}
}
}
客户端调用方:
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
private readonly HubConnection _hubConnection;
public bool Running { get; set; }
public string Status { get; set; }
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
_hubConnection = new HubConnectionBuilder()
.WithUrl("https://localhost:7127/myhub") // Replace with the actual URL of your SignalR hub
.Build();
_ = _hubConnection.StartAsync();
_hubConnection.On<bool>("SendStatus", (status) =>
{
Running = status;
});
}
public async Task OnGetAsync()
{
// Start the SignalR connection
}
public async Task<IActionResult> OnPostOpenAsync(IFormCollection data)
{
Status = _hubConnection.State.ToString();
return Page();
}
public async Task<IActionResult> OnPostStartAsync(IFormCollection data)
{
_ = _hubConnection.InvokeAsync("StartLongRunningTask");
Status = _hubConnection.State.ToString();
return Page();
}
public async Task<IActionResult> OnPostCancelAsync(IFormCollection data)
{
await _hubConnection.InvokeAsync("CancelLongRunningTask");
Status = _hubConnection.State.ToString();
return Page();
}
}
你可以猜到打开、启动和取消功能后面有按钮。但这里发生了几件有趣的事情。
如果我等待 LongRunningTask 的调用,它会等到它完成,但永远不会等到我取消它。到目前为止,这对我来说是合乎逻辑的。
但是当我不等待 LongRunningTask(CancellationTokenSource.Token);在服务器中心。它不应该等待这个任务完成,对吧?还是我在这里遗漏了什么?
我不确定为什么,但是Clients.Caller.SendStatus(true)不会发送任何内容,直到我将其移动到服务器中心中的StartlongRunningTask()。我能想到一个原因,但我不确定,如果有人能向我解释这种行为,那就太好了。
最后但并非最不重要的一点是,我写这篇文章的核心原因。如果我在客户端等待调用,它将在我单击取消之前等待。但是当我这样做时,结果是内存和 CPU 使用率:
第一条蓝线是我启动长时间运行的任务的位置,第二条蓝线是我取消任务的位置。我相信不知何故,任务一直在后台运行,但一旦我单击,前端就会停止加载,并且触发了服务器集线器上的“CancelLongRunningTask”。那么这到底是怎么发生的呢?前端停止加载,但在后台保持循环?如果有人能向我解释这种行为,那就太好了!
编辑
在等待了 Canton7 建议的一切之后,还有一个问题需要回答,因为 CancelLongRunningTask() 函数在空令牌上调用 Cancel?除此之外,.SendStatus(true) 从不发送?
答:
请注意,由于您选中了 ,因此您可能会(也可能不会,具体取决于此测试还是第一个发现取消已发生的测试)通过返回而不是抛出异常来响应取消。在这种情况下,您的不会运行。如果您使用异常来处理取消,最好避免检查和使用 。!ct.IsCancellationRequested
Task.Delay
// Handle cancellation logic here
ct.IsCancellationRequested
ct.ThrowIfCancellationRequested()
另一个问题是你没有 ,所以你会尽可能快地绕着这个循环旋转,连续调用。您无需等待对任何一方的调用,因此您甚至不需要等待上一次发送完成再发送另一次发送。await
Task.Delay
while
Clients.Caller.SendStatus(true)
SendStatus
前端可能停止加载,因为它被连续的状态流发送垃圾邮件。
让我们把事情做好,并放入适当的.这将阻止您的循环旋转,并意味着正确等待完成,而不会阻塞线程。async
awaits
while
StartLongRunningTask
LongRunningTask
我之所以这样做,是因为被调用者可能不想等到完成。我们捕获所有异常(使用 ),因此该方法不会使我们的应用程序崩溃。StartLongRunningTask
async void
LongRunningTask
catch (Exception ex)
async void
我还抓住了哪个是基类并涵盖更多案例。OperationCanceledException
TaskCanceledException
public class MyHub : Hub<IMyHub>
{
private CancellationTokenSource cancellationTokenSource;
public async void StartLongRunningTask()
{
cancellationTokenSource = new CancellationTokenSource();
try
{
await LongRunningTask(cancellationTokenSource.Token);
// Task completed successfully
}
catch (OperationCanceledException)
{
// Task was canceled by the client
// Handle cancellation logic here
}
catch (Exception ex)
{
// Handle failure here
}
finally
{
cancellationTokenSource.Dispose();
}
}
public async void CancelLongRunningTask()
{
cancellationTokenSource?.Cancel();
await Clients.All.SendStatus(false);
}
private async Task LongRunningTask(CancellationToken ct)
{
// Your long-running task implementation
// Ensure to check the cancellation token periodically and stop processing if canceled
while (true)
{
ct.ThrowIfCancellationRequested();
await Clients.Caller.SendStatus(true);
await Task.Delay(1000, ct);
// Continue with other work...
}
}
}
评论
!ct.IsCancellationRequested
Task.Delay
// Handle cancellation logic here
ct.IsCancellationRequested
ct.ThrowIfCancellationRequested()
await
Task.Delay
while
Clients.Caller.SendStatus(true)
SendStatus
_ =
StartLongRunningTask
CancelLongRunningTask
CancelLongRunningTask
StartLongRunningTask
cancellationTokenSource