C# 使用TPL的推测性执行

C# 使用TPL的推测性执行,c#,task-parallel-library,plinq,C#,Task Parallel Library,Plinq,我有一个列表,我想并行枚举它,查找第一个要完成的任务,结果为true,并且不等待或观察任何其他仍挂起的任务的异常 var tasks = new List<Task<bool>> { Task.Delay(2000).ContinueWith(x => false), Task.Delay(0).ContinueWith(x => true), }; 它并行执行,但不会在找到满意的结果时立即返回。因为访问结果属性是阻塞的。为了使用PLI

我有一个
列表
,我想并行枚举它,查找第一个要完成的任务,结果为
true
,并且不等待或观察任何其他仍挂起的任务的异常

var tasks = new List<Task<bool>>
{ 
    Task.Delay(2000).ContinueWith(x => false), 
    Task.Delay(0).ContinueWith(x => true), 
};
它并行执行,但不会在找到满意的结果时立即返回。因为访问结果属性是阻塞的。为了使用PLINQ实现这一点,我必须写下以下令人敬畏的声明:

var cts = new CancellationTokenSource();
var task = tasks.AsParallel()
    .FirstOrDefault(t =>
    {
        try 
        { 
            t.Wait(cts.Token);
            if (t.Result)
            {
                cts.Cancel();
            }

            return t.Result;
        } 
        catch (OperationCanceledException) 
        { 
            return false;
        }
    } );
我已经编写了一个扩展方法,可以在任务完成时生成这样的任务

public static class Exts
{
    public static IEnumerable<Task<T>> InCompletionOrder<T>(this IEnumerable<Task<T>> source)
    {
        var tasks = source.ToList();
        while (tasks.Any())
        {
            var t = Task.WhenAny(tasks);
            yield return t.Result;
            tasks.Remove(t.Result);
        }
    }
}

// and run like so
var task = tasks.InCompletionOrder().FirstOrDefault(t => t.Result);
公共静态类Exts
{
公共静态IEnumerable不完整顺序(此IEnumerable源)
{
var tasks=source.ToList();
while(tasks.Any())
{
var t=任务。WhenAny(任务);
收益率t.结果;
任务。删除(t.Result);
}
}
}
//然后像这样跑
var task=tasks.completionorder().FirstOrDefault(t=>t.Result);

但感觉这是一件很普通的事情,有更好的方法。建议?

也许是这样的

var tcs = new TaskCompletionSource<Task<bool>>();

foreach (var task in tasks)
{
    task.ContinueWith((t, state) =>
    {
        if (t.Result)
        {
            ((TaskCompletionSource<Task<bool>>)state).TrySetResult(t);
        }
    },
        tcs,
        TaskContinuationOptions.OnlyOnRanToCompletion |
        TaskContinuationOptions.ExecuteSynchronously);
}

var firstTaskToComplete = tcs.Task;
var tcs=new TaskCompletionSource();
foreach(任务中的var任务)
{
task.ContinueWith((t,state)=>
{
如果(t.Result)
{
((TaskCompletionSource)状态);
}
},
tcs,
TaskContinuationOptions.OnlyOnRanToCompletion|
TaskContinuationOptions.ExecuteSynchronously);
}
var firstTaskToComplete=tcs.Task;

也许您可以试试Rx.Net库。它实际上非常有利于提供Linq来工作

在引用Microsoft Rx.Net程序集后,请在LinqPad中尝试此代码段

using System
using System.Linq
using System.Reactive.Concurrency
using System.Reactive.Linq
using System.Reactive.Threading.Tasks
using System.Threading.Tasks

void Main()
{
    var tasks = new List<Task<bool>>
    { 
        Task.Delay(2000).ContinueWith(x => false), 
        Task.Delay(0).ContinueWith(x => true), 
    };

    var observable = (from t in tasks.ToObservable()
                      //Convert task to an observable
                      let o = t.ToObservable()
                      //SelectMany
                      from x in o
                      select x);


    var foo = observable
                .SubscribeOn(Scheduler.Default) //Run the tasks on the threadpool
                .ToList()
                .First();

    Console.WriteLine(foo);
}
使用系统
使用System.Linq
使用System.Reactive.Concurrency
使用System.Reactive.Linq
使用System.Responsive.Threading.Tasks
使用System.Threading.Tasks
void Main()
{
var tasks=新列表
{ 
Task.Delay(2000).ContinueWith(x=>false),
Task.Delay(0).ContinueWith(x=>true),
};
var observable=(来自tasks.ToObservable()中的t)
//将任务转换为可观察任务
设o=t.ToObservable()
//选择许多
从x到o
选择x);
var foo=可观察
.SubscribeOn(Scheduler.Default)//在线程池上运行任务
托利斯先生()
.First();
控制台写入线(foo);
}

首先,我不明白您为什么要在这里使用PLINQ。枚举
任务的列表应该不会花费太长时间,所以我认为并行化不会带来任何好处

现在,要获取已使用
true
完成的第一个
任务
,您可以使用:

如果您想获得一组任务,按完成情况排序,请阅读Stephen Toub的文章。如果要首先列出那些返回
true
的代码,则需要修改该代码。如果不想修改它,可以使用



另外,在您问题中的特定情况下,您可以通过向PLINQ查询添加
.WithMergeOptions(ParallelMergeOptions.NotBuffered)
来“修复”代码。但这样做在大多数情况下仍然不起作用,即使这样做也会浪费大量线程。这是因为PLINQ使用固定数量的线程,而分区和使用
Result
将在大部分时间阻塞这些线程。

这段代码实际上会执行这两个任务…如果您只想得到返回的第一个结果…远程.ToList()我过去也曾使用过它,我不是真的想在我的项目中加入rx。我运行您的代码只是出于好奇,在删除ToList并将First(已过时)更改为FirstOrDefaultAsync之后,我得到了我想要的正确行为。感谢您提供了完整的可复制/粘贴的代码段,尽管+1我认为您应该使用
TrySetResult()
,以避免未捕获的异常(尽管它们实际上不会影响.Net 4.5上的程序)。我不知道OP的要求,所以我不知道任务是否应该取消。但是关于TrySetResult,你说得有道理,谢谢!修正。我不是指取消集合中的
任务,我只是指继续。设置结果后,无需执行后续操作的其余部分(但也不会对性能造成太大影响)。我喜欢这里的概念,但是,我还需要知道是否所有任务都未成功完成。我已经找到了一种方法,通过
Task.WaitAny(Task.WhenAll(tasks),tcs.Task)==1谢谢你的回答。如果你只是列举任务列表并检查IsCompleted,那么如果任务还没有完成,FirstOrDefault将返回null,而不是等到第一个任务完成。@dtb是的,我假设至少有一个
任务已经完成,但实际上没有办法知道这一点。我想它在某些情况下可能仍然有用。@dtb在这里是正确的,我正在检查的所有任务在我的情况下都有时间完成。然而,那篇Toub的文章却恰到好处+仅此一项就有1个。我在发帖前浏览了一下网页,但那块宝石让我为自己的谷歌搜索技能感到尴尬。干杯,你是对的,并行处理任务没有意义
using System
using System.Linq
using System.Reactive.Concurrency
using System.Reactive.Linq
using System.Reactive.Threading.Tasks
using System.Threading.Tasks

void Main()
{
    var tasks = new List<Task<bool>>
    { 
        Task.Delay(2000).ContinueWith(x => false), 
        Task.Delay(0).ContinueWith(x => true), 
    };

    var observable = (from t in tasks.ToObservable()
                      //Convert task to an observable
                      let o = t.ToObservable()
                      //SelectMany
                      from x in o
                      select x);


    var foo = observable
                .SubscribeOn(Scheduler.Default) //Run the tasks on the threadpool
                .ToList()
                .First();

    Console.WriteLine(foo);
}
var task = tasks.FirstOrDefault(t => t.IsCompleted && t.Result);