【问题标题】:How to cancel other threads as soon as one completed thread satisfies condition一旦一个完成的线程满足条件,如何取消其他线程
【发布时间】:2015-06-01 15:48:11
【问题描述】:

我有一个 ASP.NET MVC 应用程序,它需要检查 3 个远程 API 服务器上是否存在某些东西。应用程序将一个 ID 传递给每个 API,它返回 true 或 false。代码如下所示。

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 Action 才能返回。

只要一个 API 返回 true,我想立即在 Action 上返回 true,而不是等待其他任务完成。

C# Task 类有 .WaitAll 和 .WaitAny,但它们也不起作用。由于我需要取消另一个 HttpClient 请求,我想我需要使用 CancellationToken 但我不知道如何将它与这个结构一起使用。

干杯。

【问题讨论】:

  • 您可以使用.WhenAny,然后使用.ContinueWith 取消其他操作...

标签: c# async-await task dotnet-httpclient cancellation-token


【解决方案1】:

如果要立即返回,可以使用Task.WhenAny 代替Task.WhenAll。这不会取消正在进行的任务,但可以让您尽快返回:

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

如果它们无关紧要,我会像上面一样简单地丢弃它们。

【讨论】:

  • 这是否会在返回时取消未完成的任务,记住 HttpClient 仅提供异步方法 (HttpClient.SendAsync),因此 checkServer 方法可能仍在等待?
  • @peter 不,这不会取消任务。但是,如果您希望取消它们,您可以将令牌传递给 SendAsync 并在获得第一个 true 后调用 cts.Cancel()
  • @peter 为什么你认为不是?
  • 认为它可能会使 CPU 旋转吗?不确定 while 如何与 async 方法交互。
  • @peter 它将异步等待任务完成。它不会像没有睡眠的同步循环那样旋转 CPU。
【解决方案2】:

通过使用以下方法来获取一系列任务并根据它们完成的时间对它们进行排序,这个问题变得更容易了。

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;
}

【讨论】:

  • 1.您可以简单地返回 taskSources.Select(tcs =&gt; tcs.Task).ToList() 并丢失列表。 2. 我也会使用TaskContinuationOptions.ExecuteSynchronously。否则非常好的和原始的解决方案。
  • @i3arnon 1. 如果任何任务立即完成,则不会。当您完成任务列表的迭代时,您最终可能已经从 BC 中取出了一项任务(在后续中)。
【解决方案3】:

您可以等待第一个任务完成 - 如果它成功,立即返回 true。否则,等待下一个完成,以此类推。

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;
}

【讨论】:

  • @peter.swallow 是的,当然。它以清晰简洁的方式传达了您的意图:“在列表不为空时执行此操作”
猜你喜欢
  • 2019-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-24
  • 2017-04-02
  • 1970-01-01
相关资源
最近更新 更多