首先,请原谅我的英语。我将简要介绍一下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();
}
}
}
我做错了什么?
感谢。
答案 0 :(得分:3)
代码中的问题是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 ms Task finished 5000 waiting semaphore (will wait 5010 ms) waiting 5010 ms Task finished 5010 waiting semaphore (will wait 5020 ms) waiting 5020 ms Task finished 5020 waiting semaphore (will wait 5030 ms) waiting 5030 ms Task finished 5030 waiting semaphore (will wait 5040 ms) waiting 5040 ms Task finished 5040
正如您所看到的,在上一个任务完成之前,您甚至都看不到&#34;等待信号量...&#34; 消息。这是因为在select
方法返回当前元素的值之前,LINQ MethodAsync()
无法继续执行序列中的下一个元素,并且直到Thread.Sleep()
才会发生这种情况。完成。
您可能认为await semaphore.WaitAsync()
应该向调用者屈服,允许返回Task
并继续select
的枚举。但是,只有在信号量不可用时才会发生这种情况。它在每次通话时都可用,因为每次通话仅在前一次通话完成后才会发生,因为当前一次通话完成时,信号量可用。由于在调用WaitAsync()
时信号量可用,await
同步完成。即代码直接进入Thread.Sleep()
而不是屈服于调用者。
最终效果是WhenAny()
的调用甚至不会发生{em>所有 Thread.Sleep()
次调用(当然,所有semaphore.WaitAsync()
1}}调用也已完成。
据推测,这出现在一些真实场景中,您发布的代码仅用于演示目的。所以,很难确切地说出你应该解决的问题。但是,在您发布的代码示例中,仅切换到Task.Delay()
而不是Thread.Sleep()
就足够了。由于延迟始终为非零,因此即使信号量本身可用,该方法也始终在该点处产生。这允许select
在&#34; work&#34;之前进行下一次MethodAsync()
调用。在当前的通话中已经完成。
通过这种方式,所有任务实际上是按照您最初的预期同时创建的。
无论现实代码是什么样的,你想要做的是确保在信号量获取后你有一个异步操作,以允许该方法实际返回到调用者,以便下一个操作也可以开始。