4

私はList<Task<bool>>並行して列挙したいことを持っています。最初のタスクを見つけて、結果を完了し、trueまだ保留中の他のタスクの例外を待機または観察しません。

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

私はPLINQを使って次のようなことをしようとしました:

var task = tasks.AsParallel().FirstOrDefault(t => t.Result);

これは並行して実行されますが、満足のいく結果が見つかるとすぐには戻りません。Result プロパティへのアクセスがブロックされているためです。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);

しかし、これはより良い方法があるほど一般的なことのように感じます. 提案?

4

3 に答える 3

3

たぶん、このようなものですか?

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;
于 2013-02-06T01:54:16.297 に答える
1

まず、なぜここで PLINQ を使用しようとしているのかわかりません。s のリストを列挙するTaskのに時間はかからないはずなので、並列化しても何も得られないと思います。

ここで、Taskですでに完了している最初のものを取得するには、(非ブロッキング)プロパティtrueを使用できます。IsCompleted

var task = tasks.FirstOrDefault(t => t.IsCompleted && t.Result);

のコレクションを取得したい場合はTask、Stephen Toub の記事Processing tasks as they complete を参照してください。最初に返されるものをリストしたい場合はtrue、そのコードを変更する必要があります。変更したくない場合は、Stephen Cleary の AsyncEx ライブラリ からこのアプローチのバージョンを使用できます。


また、質問の特定のケースでは.WithMergeOptions(ParallelMergeOptions.NotBuffered)、PLINQ クエリに追加することでコードを「修正」できます。しかし、そうしてもほとんどの場合はうまくいきません。これは、PLINQ が一定数のスレッドを使用し、パーティショニングと使用Resultがほとんどの場合、これらのスレッドをブロックするためです。

于 2013-02-06T09:14:09.623 に答える
1

おそらく、Rx.Net ライブラリを試すことができます。Linq to Work を効果的に提供するのに非常に適しています。

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);
}
于 2013-02-06T02:07:07.697 に答える