C# 具有指定结果的任务并行库WaitAny

C# 具有指定结果的任务并行库WaitAny,c#,task-parallel-library,C#,Task Parallel Library,我正试图编写一些代码来并行地对多个不同的服务器进行web服务调用,因此TPL似乎是显而易见的选择 只有一个web服务调用会返回我想要的结果,而所有其他调用都不会。我正在试图找到一种有效地执行任务的方法。WaitAny,但只有在第一个符合条件的任务返回时才能解除阻止 我尝试了WaitAny,但无法确定过滤器的放置位置。我走了这么远: public void SearchServers() { var servers = new[] {"server1", "server2", "serve

我正试图编写一些代码来并行地对多个不同的服务器进行web服务调用,因此TPL似乎是显而易见的选择

只有一个web服务调用会返回我想要的结果,而所有其他调用都不会。我正在试图找到一种有效地执行
任务的方法。WaitAny
,但只有在第一个符合条件的
任务返回时才能解除阻止

我尝试了
WaitAny
,但无法确定过滤器的放置位置。我走了这么远:

public void SearchServers()
{
    var servers = new[] {"server1", "server2", "server3", "server4"};
    var tasks = servers
                 .Select(s => Task<bool>.Factory.StartNew(server => CallServer((string)server), s))
                 .ToArray();

    Task.WaitAny(tasks); //how do I say "WaitAny where the result is true"?

    //Omitted: cancel any outstanding tasks since the correct server has been found
}

private bool CallServer(string server)
{
    //... make the call to the server and return the result ...
}
publicsvoidsearchservers()
{
var servers=new[]{“server1”、“server2”、“server3”、“server4”};
var任务=服务器
.Select(s=>Task.Factory.StartNew(服务器=>CallServer((字符串)服务器),s))
.ToArray();
Task.WaitAny(tasks);//如果结果为真,我怎么说“WaitAny”?
//省略:取消所有未完成的任务,因为找到了正确的服务器
}
专用布尔调用服务器(字符串服务器)
{
//…调用服务器并返回结果。。。
}
编辑:快速澄清,以防上面出现任何混淆。我正在努力做到以下几点:

  • 对于每个服务器,启动一个
    任务来检查它
  • 或者,等待服务器返回true(最多只有1台服务器会返回true)
  • 或者,等待所有服务器返回false,即没有匹配

  • 我能想到的最好的方法是为每个
    任务
    指定一个
    ContinueWith
    ,检查结果,如果
    为true
    ,则取消其他任务。用于取消您可能要使用的任务

    更新:另一种解决方案是
    WaitAny
    ,直到正确的任务完成(但它有一些缺点,例如从列表中删除已完成的任务,并从剩余的任务中创建一个新数组是一项相当繁重的操作):


    使用Interlocated.CompareExchange可以做到这一点,只有一个任务能够在serverReturedData上写入

        public void SearchServers()
            {
                ResultClass serverReturnedData = null;
                var servers = new[] {"server1", "server2", "server3", "server4"};
                var tasks = servers.Select(s => Task<bool>.Factory.StartNew(server => 
                {
                   var result = CallServer((string)server), s);
                   Interlocked.CompareExchange(ref serverReturnedData, result, null);
    
                }).ToArray();
    
                Task.WaitAny(tasks); //how do I say "WaitAny where the result is true"?
            //
            // use serverReturnedData as you want.
            // 
            }
    
    publicsvoidsearchservers()
    {
    ResultClass serverReturnedData=null;
    var servers=new[]{“server1”、“server2”、“server3”、“server4”};
    var tasks=servers.Select(s=>Task.Factory.StartNew(server=>
    {
    var result=CallServer((字符串)server),s);
    Interlocated.CompareExchange(参考服务器返回数据,结果,空);
    }).ToArray();
    Task.WaitAny(tasks);//如果结果为真,我怎么说“WaitAny”?
    //
    //根据需要使用serverReturnedData。
    // 
    }
    
    编辑:正如Jasd所说,上述代码可以在变量serverReturnedData具有有效值之前返回(如果服务器返回空值,则可能发生这种情况),以确保您可以将结果包装到自定义对象中。

    您可以使用它,它会在任务完成时返回任务。您的代码可能类似于:

    var tasks = servers
        .Select(s => Task.Factory.StartNew(server => CallServer((string)server), s))
        .OrderByCompletion();
    
    foreach (var task in tasks)
    {
        if (task.Result)
        {
            Console.WriteLine("found");
            break;
        }
        Console.WriteLine("not found yet");
    }
    
    // cancel any outstanding tasks since the correct server has been found
    

    以下是基于svick答案的通用解决方案:

    public static async Task<T> GetFirstResult<T>(
    this IEnumerable<Func<CancellationToken, Task<T>>> taskFactories, 
    Action<Exception> exceptionHandler,
    Predicate<T> predicate)
    {
        T ret = default(T);
        var cts = new CancellationTokenSource();
        var proxified = taskFactories.Select(tf => tf(cts.Token)).ProxifyByCompletion();
        int i;
        for (i = 0; i < proxified.Length; i++)
        {
            try
            {
                ret = await proxified[i].ConfigureAwait(false);
            }
            catch (Exception e)
            {
                exceptionHandler(e);
                continue;
            }
            if (predicate(ret))
            {
                break;
            }
        }
    
        if (i == proxified.Length)
        {
            throw new InvalidOperationException("No task returned the expected value");
        }
        cts.Cancel(); //we have our value, so we can cancel the rest of the tasks
        for (int j = i+1; j < proxified.Length; j++)
        {
            //observe remaining tasks to prevent process crash 
            proxified[j].ContinueWith(
             t => exceptionHandler(t.Exception), TaskContinuationOptions.OnlyOnFaulted)
                       .Forget();
        }
        return ret;
    }
    
    Forget
    是一种抑制CS4014的空方法:

    public static void Forget(this Task task) //suppress CS4014
    {
    }
    

    Task.WaitAny(任务)之后
    serverReturnedData
    仍然可以更改(因为其他任务将要完成)。此外,不能保证完成的第一个任务是返回
    true
    的任务。上面的代码保证第一个非空值将存储在局部变量上,如果返回空值的第一个任务返回空值,则随后将更改。但这可以通过将结果包装在自定义对象中轻松解决。Ok。首先,bool是一种值类型,不能为null,因此您可能需要将初始状态和
    Interlocked.compareeexchange
    的第三个参数更改为false。此外,完成的第一个任务(您在
    task.WaitAny(tasks);
    中等待的任务)不能保证返回
    true
    。但是在我看来,OP想要等待返回
    true
    的第一个任务。true改变了我的示例,没有注意到bool声明xD。第二个可能是我在阅读问题时的误解,问题不
    任务。结果
    在下一个任务完成之前阻止当前线程?如果当前线程是UI线程,那么可能还应该在另一个线程上执行此操作?@Jasd是的,它是。但是这个问题需要改进的
    WaitAny()
    ,它也会阻塞。因此,我假设这不是一个UI应用程序,或者它已经在一个单独的线程上运行。这看起来非常整洁,但不幸的是我使用的是VS2010,所以我不能使用该库。@AdamRodger在这种情况下,您可以使用,或者,这有点不同(它返回
    IEnumerable
    ).Jon Skeet的代码已移动。我已尝试了您的代码示例,但它似乎没有达到我所希望的效果。我不能使用
    WaitAny
    ,因为它只在第一个
    任务完成时返回,即使服务器不是正确的。我也不能使用
    WaitAll
    ,否则即使在找到正确的任务后,我也必须等待所有任务完成。理想情况下,我希望执行“等待一个任务返回true或所有任务完成(即没有一个服务器匹配)”。有什么方法可以做到这一点吗?更新了我的答案,但现在我更喜欢@svick的答案。标记为答案是因为我无法使用@svick的答案,尽管它看起来很好,因为我在.Net 4.0上。再次更新了我的答案,Rx rocks:-)
        public void SearchServers()
            {
                ResultClass serverReturnedData = null;
                var servers = new[] {"server1", "server2", "server3", "server4"};
                var tasks = servers.Select(s => Task<bool>.Factory.StartNew(server => 
                {
                   var result = CallServer((string)server), s);
                   Interlocked.CompareExchange(ref serverReturnedData, result, null);
    
                }).ToArray();
    
                Task.WaitAny(tasks); //how do I say "WaitAny where the result is true"?
            //
            // use serverReturnedData as you want.
            // 
            }
    
    var tasks = servers
        .Select(s => Task.Factory.StartNew(server => CallServer((string)server), s))
        .OrderByCompletion();
    
    foreach (var task in tasks)
    {
        if (task.Result)
        {
            Console.WriteLine("found");
            break;
        }
        Console.WriteLine("not found yet");
    }
    
    // cancel any outstanding tasks since the correct server has been found
    
    public static async Task<T> GetFirstResult<T>(
    this IEnumerable<Func<CancellationToken, Task<T>>> taskFactories, 
    Action<Exception> exceptionHandler,
    Predicate<T> predicate)
    {
        T ret = default(T);
        var cts = new CancellationTokenSource();
        var proxified = taskFactories.Select(tf => tf(cts.Token)).ProxifyByCompletion();
        int i;
        for (i = 0; i < proxified.Length; i++)
        {
            try
            {
                ret = await proxified[i].ConfigureAwait(false);
            }
            catch (Exception e)
            {
                exceptionHandler(e);
                continue;
            }
            if (predicate(ret))
            {
                break;
            }
        }
    
        if (i == proxified.Length)
        {
            throw new InvalidOperationException("No task returned the expected value");
        }
        cts.Cancel(); //we have our value, so we can cancel the rest of the tasks
        for (int j = i+1; j < proxified.Length; j++)
        {
            //observe remaining tasks to prevent process crash 
            proxified[j].ContinueWith(
             t => exceptionHandler(t.Exception), TaskContinuationOptions.OnlyOnFaulted)
                       .Forget();
        }
        return ret;
    }
    
    public static Task<T>[] ProxifyByCompletion<T>(this IEnumerable<Task<T>> tasks)
    {
        var inputTasks = tasks.ToArray();
        var buckets = new TaskCompletionSource<T>[inputTasks.Length];
        var results = new Task<T>[inputTasks.Length];
        for (int i = 0; i < buckets.Length; i++)
        {
            buckets[i] = new TaskCompletionSource<T>();
            results[i] = buckets[i].Task;
        }
        int nextTaskIndex = -1;
        foreach (var inputTask in inputTasks)
        {
            inputTask.ContinueWith(completed =>
            {
                var bucket = buckets[Interlocked.Increment(ref nextTaskIndex)];
                if (completed.IsFaulted)
                {
                    Trace.Assert(completed.Exception != null);
                    bucket.TrySetException(completed.Exception.InnerExceptions);
                }
                else if (completed.IsCanceled)
                {
                    bucket.TrySetCanceled();
                }
                else
                {
                    bucket.TrySetResult(completed.Result);
                }
            }, CancellationToken.None, 
               TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
        }
        return results;
    }
    
    public static void Forget(this Task task) //suppress CS4014
    {
    }