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 の答えはおそらくより単純であり、したがって望ましいものです。