gpt4 book ai didi

C#。取消任务时,SemaphoreSlim.WaitAsync 不会抛出 OperationCanceledException

转载 作者:太空宇宙 更新时间:2023-11-03 22:49:17 25 4
gpt4 key购买 nike

首先,请原谅我的英语。我将在 Task.WhenAny 之后的附加代码中进行简要说明,我期望的是五个任务中至少有三个将被取消,但都圆满结束。当任务被取消时,SemaphoreSlim.WaitAsync 不会抛出 OperationCanceledException。

class Program
{
private static CancellationTokenSource methodRequests = new CancellationTokenSource();
private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);

static void Main(string[] args)
{
int[] delays = new int[] { 5000, 5010, 5020, 5030, 5040 };

IEnumerable<Task> tasks = from delay in delays select MethodAsync(delay, new CancellationTokenSource().Token);

Task.WhenAny(tasks).Wait();

methodRequests.Cancel();

Console.ReadKey();
}

static async Task MethodAsync(int milliseconds, CancellationToken cancellationToken)
{
var methodRequest = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, methodRequests.Token);

try
{
await semaphore.WaitAsync(methodRequest.Token);

Thread.Sleep(milliseconds);

Console.WriteLine($"Task finished {milliseconds}");
}
catch (OperationCanceledException)
{
Console.WriteLine($"Task canceled {milliseconds}");
}
finally
{
semaphore.Release();
}
}
}

我做错了什么?

谢谢。

最佳答案

您的代码中的问题是 MethodAsync() 方法在 Thread.Sleep() 方法完成之前永远不会返回。这意味着在前一个任务完成之前,每个任务甚至都不会开始。这是您的代码版本,可以更清楚地说明这一点:

private static CancellationTokenSource methodRequests = new CancellationTokenSource();
private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);

static void Main(string[] args)
{
int[] delays = new int[] { 5000, 5010, 5020, 5030, 5040 };

IEnumerable<Task> tasks = from delay in delays select MethodAsync(delay, new CancellationTokenSource().Token);

Task.WhenAny(tasks).Wait();

methodRequests.Cancel();

ReadKey();
}

static async Task MethodAsync(int milliseconds, CancellationToken cancellationToken)
{
var methodRequest = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, methodRequests.Token);

try
{
WriteLine($"waiting semaphore (will wait {milliseconds} ms)");
await semaphore.WaitAsync(methodRequest.Token);

WriteLine($"waiting {milliseconds} ms");
Thread.Sleep(milliseconds);

WriteLine($"Task finished {milliseconds}");
}
catch (OperationCanceledException)
{
WriteLine($"Task canceled {milliseconds}");
}
finally
{
semaphore.Release();
}
}

输出是:

waiting semaphore (will wait 5000 ms)waiting 5000 msTask finished 5000waiting semaphore (will wait 5010 ms)waiting 5010 msTask finished 5010waiting semaphore (will wait 5020 ms)waiting 5020 msTask finished 5020waiting semaphore (will wait 5030 ms)waiting 5030 msTask finished 5030waiting semaphore (will wait 5040 ms)waiting 5040 msTask finished 5040

如您所见,在上一个任务完成之前,您甚至都不会看到“正在等待信号量...” 消息。这是因为在 MethodAsync() 方法返回当前元素的值之前,您的 LINQ select 无法继续处理序列中的下一个元素,并且直到Thread.Sleep() 完成。

您可能认为 await semaphore.WaitAsync() 应该让步给调用者,允许返回 Taskselect 的枚举 继续。但是,只有在信号量不可用时才会发生这种情况。它在每次调用时都可用,因为每次调用仅在前一个调用完成后发生,因为在进行前一个调用时,信号量可用。由于在调用 WaitAsync() 时信号量可用,因此 await 同步完成。 IE。代码直接进入 Thread.Sleep() 而不是让步给调用者。

最终效果是 WhenAny() 的调用甚至不会发生,直到 all Thread.Sleep() 调用(当然,所有 semaphore.WaitAsync() 调用也已完成)。

大概这是在某些真实场景中出现的,您发布的代码仅用于演示目的。因此,很难准确地说出您应该修复什么。但是,在您发布的代码示例中,只需切换到 Task.Delay() 而不是 Thread.Sleep() 就足够了。由于延迟始终不为零,因此该方法将始终在该点产生,即使信号量本身可用。这允许 select 在当前调用中的“工作”完成之前继续对 MethodAsync() 的下一次调用。

通过这种方式,所有任务实际上都按照您最初的预期同时创建。

无论现实世界的代码是什么样子,您要做的是确保在获取信号量后有一个异步操作,以允许该方法实际返回给调用者,以便下一个操作( s) 也可以启动。

关于C#。取消任务时,SemaphoreSlim.WaitAsync 不会抛出 OperationCanceledException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48254614/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com