【问题标题】:Limiting the number of parallel task with SemaphoreSlim - why does it work?使用 SemaphoreSlim 限制并行任务的数量 - 为什么它会起作用?
【发布时间】:2021-03-15 09:16:28
【问题描述】:

在 MS Docu 中,您可以阅读有关 SemaphoreSlim 的信息: “代表 Semaphore 的轻量级替代方案,它限制了可以同时访问资源或资源池的线程数。”
https://docs.microsoft.com/en-us/dotnet/api/system.threading.semaphoreslim?view=net-5.0

据我了解,Task 与 Thread 不同。任务比线程更高级别。不同的任务可以在同一个线程上运行。或者可以在另一个线程上继续执行任务。
(比较:“使用异步的 .NET 中的服务器端应用程序将使用很少的线程,而不会限制自己。如果真的所有事情都可以由单个线程提供服务,那么它很可能是 - 如果你从来没有超过一件事要做就物理处理而言,那很好。” 来自in C# how to run method async in the same thread)

如果你把这些信息放在一起,IMO 得出的结论是,你不能限制使用信号量 slim 并行运行的任务数量,但是……

  • 还有其他文本给出了这种建议(How to limit the amount of concurrent async I/O operations?,请参阅“你绝对可以这样做……”)
  • 如果我在我的机器上执行此代码,它似乎是可能的。如果我为 _MaxDegreeOfParallelism 使用不同的数字和不同的数字范围,_RunningTasksCount 不会超过 MaxDegreeOfParallelism 给出的限制。

有人可以提供一些信息来澄清吗?

   class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            IRunner runner = new RunnerSemaphore();
            runner.Run();

            Console.WriteLine("Hit any key to close...");
            Console.ReadLine();
        }
    }
    public class RunnerSemaphore : IRunner
    {
        private readonly SemaphoreSlim _ConcurrencySemaphore;
        private List<int> _Numbers;
        private int _MaxDegreeOfParallelism = 3;
        private object _RunningTasksLock = new object();
        private int _RunningTasksCount = 0;

        public RunnerSemaphore()
        {
            _ConcurrencySemaphore = new SemaphoreSlim(_MaxDegreeOfParallelism);
            _Numbers = _Numbers = Enumerable.Range(1, 100).ToList();
        }

        public void Run()
        {
            RunAsync().Wait();
        }        

        private async Task RunAsync()
        {
            List<Task> allTasks = new List<Task>();            

            foreach (int number in _Numbers)
            {
                var task = Task.Run
                    (async () =>
                    {
                        await _ConcurrencySemaphore.WaitAsync();

                        bool isFast = number != 1; 
                        int delay = isFast ? 200 : 10000;

                        Console.WriteLine($"Start Work {number}\tManagedThreadId {Thread.CurrentThread.ManagedThreadId}\tRunning {IncreaseTaskCount()} tasks");
                        await Task.Delay(delay).ConfigureAwait(false);
                        Console.WriteLine($"End Work {number}\tManagedThreadId {Thread.CurrentThread.ManagedThreadId}\tRunning {DecreaseTaskCount()} tasks");
                    })
                    .ContinueWith((t) =>
                    {
                        _ConcurrencySemaphore.Release();
                    });


                allTasks.Add(task);
            }

            await Task.WhenAll(allTasks.ToArray());
        }

        private int IncreaseTaskCount()
        {
            int taskCount;
            lock (_RunningTasksLock)
            {                
                taskCount = ++ _RunningTasksCount;
            }
            return taskCount;
        }

        private int DecreaseTaskCount()
        {
            int taskCount;
            lock (_RunningTasksLock)
            {
                taskCount = -- _RunningTasksCount;
                 
            }
            return taskCount;
        }        
    }

【问题讨论】:

  • 我已多次阅读您的问题,但我不完全理解您关于为什么它不起作用的问题。 SemaphoreSlimWaitAsync 方法不会阻塞线程,它会暂停执行,然后在可能的情况下继续执行。
  • 它基本上是一个上下文不敏感的计数器(无论是线程还是任务)。每次WaitWaitAsync 时它都会递减,Release 时递增。
  • 致菲尔多:谢谢。这是我之前弄错的信息。我认为对 Wait 和 Release 的调用是上下文敏感的,并且绑定到调用线程。也许我把它和锁的工作方式混为一谈了,线程的身份,即获取锁,确实很重要。
  • 是的,这正是我所怀疑的。如果你做一些可重入的事情,你可以很容易地测试行为。

标签: c# task


【解决方案1】:

代表信号量的轻量级替代方案,它限制可以同时访问资源或资源池的线程数。

嗯,当SemaphoreSlim 首次引入时,这是一个非常好的描述——它只是一个轻量级的Semaphore。从那时起,它获得了新的方法(即WaitAsync),使其能够像异步同步原语一样工作。

据我了解,Task 与 Thread 不同。任务比线程更高级别。不同的任务可以在同一个线程上运行。或者可以在另一个线程上继续执行任务。

这适用于我所说的“委托任务”。还有一个completely different kind of Task,我称之为“承诺任务”。 Promise 任务类似于其他语言(例如 JavaScript)中的promises (or "futures"),它们只是表示某个事件的完成。 Promise 任务不会在任何地方“运行”;它们只是根据一些未来的事件(通常通过回调)完成。

async 方法总是返回承诺任务。异步方法中的代码实际上并没有作为任务的一部分运行;任务本身仅代表async 方法的完成。我推荐我的async intro 以获取有关async 以及如何安排代码部分的更多信息。

如果你把这些信息放在一起,结论是你不能限制使用信号量 slim 并行运行的任务的数量

这是个人喜好,但我尽量对术语非常小心,以避免出现此类问题。委托任务可以并行运行,例如Parallel。 Promise 任务不会“运行”,它们也不会“并行”运行,但是您可以有多个 并发 个 Promise 任务,它们都在进行中。而SemaphoreSlimWaitAsync 是限制这种并发的完美搭配。

您可能希望阅读Stephen Toub's AsyncSemaphore(以及该系列中的其他文章)。它与SemaphoreSlim 的实现不同,但就承诺任务而言,其行为基本相同。

【讨论】:

    猜你喜欢
    • 2020-01-30
    • 2022-01-24
    • 1970-01-01
    • 1970-01-01
    • 2019-09-02
    • 2011-02-23
    • 1970-01-01
    • 2021-07-09
    • 1970-01-01
    相关资源
    最近更新 更多