假设我有一个简单的UWP应用程序(因此没有与此情况无关的.NET 5或C# 8),有许多包含按钮的页面,所有这些页面都必须能够通过调用SeriousWorkAsync和FunWorkAsync开始工作。
public async Task SeriousWorkAsync(SeriousObject obj)
{
Setup(obj);
for (int i = 0; i < 10000; i++)
{
await SeriousThingAsync(i);
}
}
public async Task FunWorkAsync(FunObject obj)
{
Setup(obj);
for (int i = 0; i < 10000; i++)
{
await FunnyThingAsync(i);
}
}我的要求如下:
任何一个按钮都不能在任何时候被禁用。concurrently.
SeriousWorkAsync,我想要FunWorkAsync完成执行,在取消完成之后,SeriousWorkAsync应该执行,如果我调用SeriousWorkAsync而另一个调用正在执行,我必须取消另一个调用,而更新的调用只应该在取消完成之后执行。H 216H 117如果有额外的调用,那么第一个调用应该首先取消,只有最后一次调用才应该执行.到目前为止,我能想到的最好的解决方案是在一个循环中延迟任务,直到另一个被取消,并在方法完成执行后立即设置几个布尔标志:
private bool IsDoingWork = false;
private bool ShouldCancel = false;
public async Task FunWorkAsync(FunObject obj)
{
CancelPendingWork();
while (IsDoingWork)
{
await Task.Delay(30);
}
IsDoingWork = true;
Setup(obj);
for (int i = 0; i < 10000; i++)
{
if (ShouldCancel)
{
break;
}
await FunnyThingAsync(i);
}
IsDoingWork = false;
}
private void CancelPendingWork()
{
if (IsDoingWork)
{
ShouldCancel = true;
}
}然而,这似乎是一个非常肮脏的解决办法,而且它不能满足我的最后一个需求。我知道我应该使用CancellationToken,但到目前为止,我使用它的尝试没有成功,即使经过了大量的搜索和头脑风暴。那我该怎么做呢?
发布于 2022-05-03 02:32:14
经过大量的搜索,我偶然发现了"A pattern for self-cancelling and restarting task“。这正是我所需要的,经过一些调整,我可以放心地说,我得到了我想要的。我的实施如下:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// The task that is currently pending.
/// </summary>
private Task _pendingTask = null;
/// <summary>
/// A linked token source to control Task execution.
/// </summary>
private CancellationTokenSource _tokenSource = null;
/// <summary>
/// Does some serious work.
/// </summary>
/// <exception cref="OperationCanceledException">Thrown when the
/// operation is cancelled.</exception>
public async Task SeriousWorkAsync(CancellationToken token)
{
await CompletePendingAsync(token);
this._pendingTask = SeriousImpl(this._tokenSource.Token);
await this._pendingTask;
}
/// <summary>
/// Does some fun work.
/// </summary>
/// <exception cref="OperationCanceledException">Thrown when the
/// operation is cancelled.</exception>
public async Task FunWorkAsync(CancellationToken token)
{
await CompletePendingAsync(token);
this._pendingTask = FunImpl(this._tokenSource.Token);
await this._pendingTask;
}
/// <summary>
/// Cancels the pending Task and waits for it to complete.
/// </summary>
/// <exception cref="OperationCanceledException">If the new token has
/// been canceled before the Task, an exception is thrown.</exception>
private async Task CompletePendingAsync(CancellationToken token)
{
// Generate a new linked token
var previousCts = this._tokenSource;
var newCts = CancellationTokenSource.CreateLinkedTokenSource(token);
this._tokenSource = newCts;
if (previousCts != null)
{
// Cancel the previous session and wait for its termination
previousCts.Cancel();
try { await this._pendingTask; } catch { }
}
// We need to check if we've been canceled
newCts.Token.ThrowIfCancellationRequested();
}理想情况下,调用这些方法如下所示:
try
{
await SeriousWorkAsync(new CancellationToken());
}
catch (OperationCanceledException) { }如果您愿意的话,您可以将方法包装在try catch中,并且总是生成一个新的令牌,这样消费者就不需要为取消应用特殊的处理:
var token = new CancellationToken();
try
{
await CompletePendingAsync(token);
this._pendingTask = FunImpl(this._tokenSource.Token);
await this._pendingTask;
}
catch { }最后,我使用以下SeriousWorkAsync和FunWorkAsync实现进行了测试
private async Task SeriousImpl(CancellationToken token)
{
Debug.WriteLine("--- Doing serious stuff ---");
for (int i = 1000; i <= 4000; i += 1000)
{
token.ThrowIfCancellationRequested();
Debug.WriteLine("Sending mails for " + i + "ms...");
await Task.Delay(i);
}
Debug.WriteLine("--- Done! ---");
}
private async Task FunImpl(CancellationToken token)
{
Debug.WriteLine("--- Having fun! ---");
for (int i = 1000; i <= 4000; i += 1000)
{
token.ThrowIfCancellationRequested();
Debug.WriteLine("Laughing for " + i + "ms...");
await Task.Delay(i);
}
Debug.WriteLine("--- Done! ---");
}发布于 2022-04-25 21:17:17
因为您使用的是任务,并且需要等待任务的完成,所以可以使用此机制在下一次执行开始之前等待。
我没有测试这段代码,但它应该能工作。
// Store current task for later
private Task CurrentTask = null;
// Create new cancellation token for cancelling the task
private CancellationTokenSource TokenSource = new CancellationTokenSource();
private object WorkLock = new object();
public async Task FunWorkAsync(FunObject obj)
{
// Define the task we will be doing
var task = new Task(async () =>
{
Setup(obj);
for (int i = 0; i < 10000; i++)
{
// Break from the task when requested
if (TokenSource.IsCancellationRequested)
{
break;
}
await FunnyThingAsync(i);
}
});
// Make sure that we do not start multiple tasks at once
lock (WorkLock)
{
if (CurrentTask != null)
{
TokenSource.Cancel();
// You should make sure here that you can continue by providing cancellation token with a timeout
CurrentTask.Wait(CancellationToken.None);
}
CurrentTask = task;
// Restart cancelation token for new task
TokenSource = new CancellationTokenSource();
task.Start();
}
await task;
}https://stackoverflow.com/questions/72005686
复制相似问题