如何在一个完成的线程满足条件后立即取消其他线程
How to cancel other threads as soon as one completed thread satisfies condition
我有一个 ASP.NET MVC 应用程序,它需要检查 3 个远程 API 服务器上是否存在某些内容。应用程序将一个 ID 传递给每个 API,它 return 为真或假。代码如下所示。
public class PingController
{
public async Task<bool> IsFound(int id)
{
var servers = new ['a.com', b.com', 'c.com'];
var result = await foundAtServers(id, servers);
return result;
}
private async Task<bool> foundAtServers(int id, string[] servers)
{
var tasks = from server in servers
select checkServer(id, server);
return await.Task.WhenAll(tasks.ToArray());
}
private async Task<bool> checkServer(id, server)
{
var request = new HttpRequestMessage(HttpMethod.Get, server+"/api/exists"+id);
var client = new HttpClient();
var task = await client.SendAsync(request);
var response = await task.Content.ReadAsStringAsync();
return bool.Parse(response);
}
}
此代码目前异步检查所有 3 个 API,但会等到所有 HttpClient 调用完成后 MVC 操作才能 return。
只要一个 API return 为真,我想立即 return 在操作上为真,而不是等待其他任务完成。
C# 任务 class 有 .WaitAll 和 .WaitAny,但它们也不起作用。由于我需要取消其他 HttpClient 请求,我想我需要使用 CancellationToken 但我不知道如何将它用于此结构。
干杯。
如果想立即return,可以用Task.WhenAny
代替Task.WhenAll
。此 不会 取消正在进行的任务,但可以让您尽快 return:
private async Task<bool> FoundAtServersAsync(int id, string[] servers)
{
var tasks = (from server in servers
select checkServer(id, server)).ToList();
while (tasks.Count > 0)
{
var finishedTask = await Task.WhenAny(tasks);
if (finishedTask.Result)
{
return finishedTask.Result;
}
tasks.Remove(finishedTask);
}
return false;
}
这将放弃其他任务。这意味着如果其中之一抛出任何异常,它将被吞噬。
编辑:
如果您关心实际取消其他任务,请考虑将您的 CancellationToken
传递给 overload of SendAsync
which takes one,并在收到值后调用 CancellationTokenSource.Cancel
。请注意,这意味着您还需要处理它们将抛出的 OperationCanceledException
。
如果它们无关紧要,我会像上面那样简单地丢弃它们。
您可以等待第一个任务完成 - 如果成功,return 立即为真。否则,等待下一个完成,依此类推。
private async Task<bool> foundAtServers(int id, string[] servers)
{
var tasks = servers.Select(server => checkServer(id, server))
.ToList();
while(tasks.Any())
{
var task = await Task.WhenAny(tasks);
if(task.Result)
return true;
tasks.Remove(task);
}
return false;
}
通过使用以下方法获取一系列任务并根据完成时间对它们进行排序,可以更轻松地解决此问题。
public static IEnumerable<Task<T>> Order<T>(this IEnumerable<Task<T>> tasks)
{
var taskList = tasks.ToList();
var taskSources = new BlockingCollection<TaskCompletionSource<T>>();
var taskSourceList = new List<TaskCompletionSource<T>>(taskList.Count);
foreach (var task in taskList)
{
var newSource = new TaskCompletionSource<T>();
taskSources.Add(newSource);
taskSourceList.Add(newSource);
task.ContinueWith(t =>
{
var source = taskSources.Take();
if (t.IsCanceled)
source.TrySetCanceled();
else if (t.IsFaulted)
source.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCompleted)
source.TrySetResult(t.Result);
}, CancellationToken.None,
TaskContinuationOptions.PreferFairness,
TaskScheduler.Default);
}
return taskSourceList.Select(tcs => tcs.Task);
}
有了这个你可以写:
public static async Task<bool> WhenAny(this IEnumerable<Task<bool>> tasks)
{
foreach (var task in tasks.Order())
if (await task)
return true;
return false;
}
我有一个 ASP.NET MVC 应用程序,它需要检查 3 个远程 API 服务器上是否存在某些内容。应用程序将一个 ID 传递给每个 API,它 return 为真或假。代码如下所示。
public class PingController
{
public async Task<bool> IsFound(int id)
{
var servers = new ['a.com', b.com', 'c.com'];
var result = await foundAtServers(id, servers);
return result;
}
private async Task<bool> foundAtServers(int id, string[] servers)
{
var tasks = from server in servers
select checkServer(id, server);
return await.Task.WhenAll(tasks.ToArray());
}
private async Task<bool> checkServer(id, server)
{
var request = new HttpRequestMessage(HttpMethod.Get, server+"/api/exists"+id);
var client = new HttpClient();
var task = await client.SendAsync(request);
var response = await task.Content.ReadAsStringAsync();
return bool.Parse(response);
}
}
此代码目前异步检查所有 3 个 API,但会等到所有 HttpClient 调用完成后 MVC 操作才能 return。
只要一个 API return 为真,我想立即 return 在操作上为真,而不是等待其他任务完成。
C# 任务 class 有 .WaitAll 和 .WaitAny,但它们也不起作用。由于我需要取消其他 HttpClient 请求,我想我需要使用 CancellationToken 但我不知道如何将它用于此结构。
干杯。
如果想立即return,可以用Task.WhenAny
代替Task.WhenAll
。此 不会 取消正在进行的任务,但可以让您尽快 return:
private async Task<bool> FoundAtServersAsync(int id, string[] servers)
{
var tasks = (from server in servers
select checkServer(id, server)).ToList();
while (tasks.Count > 0)
{
var finishedTask = await Task.WhenAny(tasks);
if (finishedTask.Result)
{
return finishedTask.Result;
}
tasks.Remove(finishedTask);
}
return false;
}
这将放弃其他任务。这意味着如果其中之一抛出任何异常,它将被吞噬。
编辑:
如果您关心实际取消其他任务,请考虑将您的 CancellationToken
传递给 overload of SendAsync
which takes one,并在收到值后调用 CancellationTokenSource.Cancel
。请注意,这意味着您还需要处理它们将抛出的 OperationCanceledException
。
如果它们无关紧要,我会像上面那样简单地丢弃它们。
您可以等待第一个任务完成 - 如果成功,return 立即为真。否则,等待下一个完成,依此类推。
private async Task<bool> foundAtServers(int id, string[] servers)
{
var tasks = servers.Select(server => checkServer(id, server))
.ToList();
while(tasks.Any())
{
var task = await Task.WhenAny(tasks);
if(task.Result)
return true;
tasks.Remove(task);
}
return false;
}
通过使用以下方法获取一系列任务并根据完成时间对它们进行排序,可以更轻松地解决此问题。
public static IEnumerable<Task<T>> Order<T>(this IEnumerable<Task<T>> tasks)
{
var taskList = tasks.ToList();
var taskSources = new BlockingCollection<TaskCompletionSource<T>>();
var taskSourceList = new List<TaskCompletionSource<T>>(taskList.Count);
foreach (var task in taskList)
{
var newSource = new TaskCompletionSource<T>();
taskSources.Add(newSource);
taskSourceList.Add(newSource);
task.ContinueWith(t =>
{
var source = taskSources.Take();
if (t.IsCanceled)
source.TrySetCanceled();
else if (t.IsFaulted)
source.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCompleted)
source.TrySetResult(t.Result);
}, CancellationToken.None,
TaskContinuationOptions.PreferFairness,
TaskScheduler.Default);
}
return taskSourceList.Select(tcs => tcs.Task);
}
有了这个你可以写:
public static async Task<bool> WhenAny(this IEnumerable<Task<bool>> tasks)
{
foreach (var task in tasks.Order())
if (await task)
return true;
return false;
}