33

10秒かかることを行う100個のタスクがあるとしましょう。ここで、一度に10個だけ実行したいと思います。たとえば、10個のうち1個が終了すると、すべてが完了するまで別のタスクが実行されます。

今はいつもThreadPool.QueueUserWorkItem()そのようなタスクに使用していましたが、そうするのは悪い習慣であり、代わりにタスクを使用する必要があることを読みました。

私の問題は、私のシナリオの良い例がどこにも見つからなかったので、タスクでこの目標を達成する方法を教えてもらえますか?

4

5 に答える 5

37
SemaphoreSlim maxThread = new SemaphoreSlim(10);

for (int i = 0; i < 115; i++)
{
    maxThread.Wait();
    Task.Factory.StartNew(() =>
        {
            //Your Works
        }
        , TaskCreationOptions.LongRunning)
    .ContinueWith( (task) => maxThread.Release() );
}
于 2012-12-28T20:20:09.283 に答える
18

TPL Dataflowは、このようなことを行うのに最適です。の100%非同期バージョンをParallel.Invoke非常に簡単に作成できます。

async Task ProcessTenAtOnce<T>(IEnumerable<T> items, Func<T, Task> func)
{
    ExecutionDataflowBlockOptions edfbo = new ExecutionDataflowBlockOptions
    {
         MaxDegreeOfParallelism = 10
    };

    ActionBlock<T> ab = new ActionBlock<T>(func, edfbo);

    foreach (T item in items)
    {
         await ab.SendAsync(item);
    }

    ab.Complete();
    await ab.Completion;
}
于 2012-12-28T21:57:39.087 に答える
9

いくつかのオプションがあります。Parallel.Invoke初心者に使用できます:

public void DoWork(IEnumerable<Action> actions)
{
    Parallel.Invoke(new ParallelOptions() { MaxDegreeOfParallelism = 10 }
        , actions.ToArray());
}

これは、正確に10個のタスクを実行するためにはるかに困難に機能し(ただし、これらのタスクを処理するスレッドプール内のスレッドの数は異なる場合があります)、Task完了するまでブロックするのではなく、終了時に指示を返す代替オプションです。

public Task DoWork(IList<Action> actions)
{
    List<Task> tasks = new List<Task>();
    int numWorkers = 10;
    int batchSize = (int)Math.Ceiling(actions.Count / (double)numWorkers);
    foreach (var batch in actions.Batch(actions.Count / 10))
    {
        tasks.Add(Task.Factory.StartNew(() =>
        {
            foreach (var action in batch)
            {
                action();
            }
        }));
    }

    return Task.WhenAll(tasks);
}

関数のMoreLinqがない場合は、次Batchのように簡単に実装できます。

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
{
    List<T> buffer = new List<T>(batchSize);

    foreach (T item in source)
    {
        buffer.Add(item);

        if (buffer.Count >= batchSize)
        {
            yield return buffer;
            buffer = new List<T>();
        }
    }
    if (buffer.Count >= 0)
    {
        yield return buffer;
    }
}
于 2012-12-28T20:04:08.423 に答える
5

TPLを使用すると思うように、私が考えることができる最も単純なソリューションを使用したいと思います。

string[] urls={};
Parallel.ForEach(urls, new ParallelOptions() { MaxDegreeOfParallelism = 2}, url =>
{
   //Download the content or do whatever you want with each URL
});
于 2014-09-01T09:15:27.403 に答える
5

次のようなメソッドを作成できます。

public static async Task RunLimitedNumberAtATime<T>(int numberOfTasksConcurrent, 
    IEnumerable<T> inputList, Func<T, Task> asyncFunc)
{
    Queue<T> inputQueue = new Queue<T>(inputList);
    List<Task> runningTasks = new List<Task>(numberOfTasksConcurrent);
    for (int i = 0; i < numberOfTasksConcurrent && inputQueue.Count > 0; i++)
        runningTasks.Add(asyncFunc(inputQueue.Dequeue()));

    while (inputQueue.Count > 0)
    {
        Task task = await Task.WhenAny(runningTasks);
        runningTasks.Remove(task);
        runningTasks.Add(asyncFunc(inputQueue.Dequeue()));
    }

    await Task.WhenAll(runningTasks);
}

そして、次のような制限付きで、任意の非同期メソッドをn回呼び出すことができます。

Task task = RunLimitedNumberAtATime(10,
    Enumerable.Range(1, 100),
    async x =>
    {
        Console.WriteLine($"Starting task {x}");
        await Task.Delay(100);
        Console.WriteLine($"Finishing task {x}");
    });

または、長時間実行される非同期メソッドを実行したい場合は、次のように実行できます。

Task task = RunLimitedNumberAtATime(10,
    Enumerable.Range(1, 100),
    x => Task.Factory.StartNew(() => {
        Console.WriteLine($"Starting task {x}");
        System.Threading.Thread.Sleep(100);
        Console.WriteLine($"Finishing task {x}");
    }, TaskCreationOptions.LongRunning));

フレームワークのどこかに同様の方法があるかもしれませんが、私はまだそれを見つけていません。

于 2018-04-25T13:58:22.470 に答える