0

次のコードが与えられた場合、インスタンスのスケジューラ、作成、および継続の設定を定義することは可能Task doThingですか?

doThingの複数のインスタンスをスケジュールして、実際に他のインスタンスから排他的に実行できるようにしたいと考えています (他のサブタスクを待っている場合でも)。

    private static async Task doThing(object i)
    {
        Console.WriteLine("in do thing {0}", (int)i);
        await Task.Delay(TimeSpan.FromSeconds(5));
        Console.WriteLine("out of do thing {0}", (int)i);
    }
    static void Main(string[] args)
    {

        CancellationTokenSource source = new CancellationTokenSource();
        ConcurrentExclusiveSchedulerPair pair = new ConcurrentExclusiveSchedulerPair(TaskScheduler.Current);

        Task Task1 = Task.Factory.StartNew((Func<object,Task>)doThing, 1, source.Token, TaskCreationOptions.AttachedToParent, pair.ExclusiveScheduler).Unwrap();
        Task Task2 = Task.Factory.StartNew((Func<object, Task>)doThing, 2, source.Token, TaskCreationOptions.AttachedToParent, pair.ExclusiveScheduler);
        Task Task3 = doThing(3);
        Task Task4 = Task.Factory.StartNew(async (i) =>
        {
            Console.WriteLine("in do thing {0}", (int)i);
            await Task.Delay(TimeSpan.FromSeconds(5));
            Console.WriteLine("out of do thing {0}", (int)i);
        }, 4, source.Token, TaskCreationOptions.None, pair.ExclusiveScheduler);
        Task.WaitAll(Task1, Task2, Task3, Task4);
        Console.ReadKey();
        return;
    }
4

2 に答える 2

5

TPL TaskSchedulers は、一度に 1 つの非同期メソッドの 1 つの同期セグメントしか表示できないため、スケジューラだけでそれを行うことはできません。しかし、より高いレベルのプリミティブを使用してそれを行うことができます。私がよく使うのは TPL Dataflow です。

まず、NuGet パッケージをインストールします。

Install-Package Microsoft.Tpl.Dataflow

次に、次のコードを使用します。

private static async Task doThing(object i) {
    Console.WriteLine("in do thing {0}", (int)i);
    await Task.Delay(TimeSpan.FromSeconds(5));
    Console.WriteLine("out of do thing {0}", (int)i);
}

static void Main(string[] args) {
    CancellationTokenSource source = new CancellationTokenSource();
    var exclusivityBlock = new ActionBlock<Func<Task>>(f => f(), new ExecutionDataflowBlockOptions { CancellationToken = source.Token }};
    exclusivityBlock.Post(() => doThing(1));
    exclusivityBlock.Post(() => doThing(2));
    exclusivityBlock.Post(() => doThing(3));
    exclusivityBlock.Post(
        async () => {
            Console.WriteLine("in do thing {0}", 4);
            await Task.Delay(TimeSpan.FromSeconds(5));
            Console.WriteLine("out of do thing {0}", 4);
        });
    exclusivityBlock.Complete();
    exclusivityBlock.Completion.Wait();
    Console.WriteLine("Done");
    Console.ReadKey();
    return;
}

このコードには、投稿された作業項目ごとに個別のタスクがありません。それが重要な場合は、次のサンプルを使用できます。

internal static class Program {
    private static async Task doThing(object i) {
        Console.WriteLine("in do thing {0}", (int)i);
        await Task.Delay(TimeSpan.FromSeconds(5));
        Console.WriteLine("out of do thing {0}", (int)i);
    }

    private static void Main(string[] args) {
        CancellationTokenSource source = new CancellationTokenSource();
        var exclusivityBlock = CreateTrackingBlock<Func<Task>>(
            f => f(), new ExecutionDataflowBlockOptions { CancellationToken = source.Token });
        var task1 = exclusivityBlock.PostWithCompletion(() => doThing(1));
        var task2 = exclusivityBlock.PostWithCompletion(() => doThing(2));
        var task3 = exclusivityBlock.PostWithCompletion(() => doThing(3));
        var task4 = exclusivityBlock.PostWithCompletion(
            async () => {
                Console.WriteLine("in do thing {0}", 4);
                await Task.Delay(TimeSpan.FromSeconds(5));
                Console.WriteLine("out of do thing {0}", 4);
            });

        Task.WaitAll(task1, task2, task3, task4);
        Console.WriteLine("Done");
        Console.ReadKey();
        return;
    }

    private static ActionBlock<Tuple<T, TaskCompletionSource<object>>> CreateTrackingBlock<T>(Func<T, Task> action, ExecutionDataflowBlockOptions options = null) {
        return new ActionBlock<Tuple<T, TaskCompletionSource<object>>>(
            async tuple => {
                try {
                    await action(tuple.Item1);
                    tuple.Item2.TrySetResult(null);
                } catch (Exception ex) {
                    tuple.Item2.TrySetException(ex);
                }
            },
            options ?? new ExecutionDataflowBlockOptions());
    }

    internal static Task PostWithCompletion<T>(this ActionBlock<Tuple<T, TaskCompletionSource<object>>> block, T value) {
        var tcs = new TaskCompletionSource<object>();
        var tuple = Tuple.Create(value, tcs);
        block.Post(tuple);
        return tcs.Task;
    }
}

ただし、Dataflow は主に個々の送信を追跡するためではなく、全体的なプロセスを追跡するために設計されているため、これは少し手間がかかることに注意してください。したがって、上記は問題なく機能しますが、Stephen Cleary の答えはおそらくより単純であり、したがって望ましいものです。

于 2013-03-03T17:21:58.477 に答える
2

次のコードが与えられた場合、タスクdoThingのインスタンスのスケジューラー、作成、および継続の設定を定義することは可能ですか?

悪いニュースは次のとおりです。いいえ、それを行う方法はありません。非ラムダタスクの「スケジューラ」を定義することは意味がありません。作成オプションは必要ありません。継続オプションは、タスク自体ではなく、継続に設定されます。

良いニュースは、この動作は必要ないということです。

非同期同期が必要です。これを行うための組み込みの方法は、次のように使用することですSemaphoreSlim

SemaphoreSlim mutex = new SemaphoreSlim(1);
private static async Task doThingAsync(object i)
{
    await mutex.WaitAsync();
    try
    {
        Console.WriteLine("in do thing {0}", (int)i);
        await Task.Delay(TimeSpan.FromSeconds(5));
        Console.WriteLine("out of do thing {0}", (int)i);
    }
    finally
    {
        mutex.Release();
    }
}

finally個人的には構文がぎこちないと思うので、代わりにを定義しIDisposableて使用usingします。

さらにパワーが必要な場合は、Stephen Toubに非同期調整プリミティブシリーズがあり、AsyncExライブラリにプリミティブの完全なスイートがあります。これらのリソースには両方ともメンバーAsyncLock付きが含まれているため、の代わりにTask<IDisposable> WaitAsync()使用できます。usingfinally

于 2013-03-01T22:08:25.650 に答える