使用异步任务和信号量的僵局

发布于 2025-02-08 23:20:51 字数 3223 浏览 2 评论 0 原文

我们正在运行ASP.NET 6 WebApplication,并在僵局中遇到了奇怪的问题。 经过数周的操作,该应用程序突然冻结了,似乎这可能是由于我们使用Smaphoreslim类的锁定机制引起的。

我试图用简单的测试项目重现这个问题,并发现了一些奇怪的东西。 以下代码只是启动1000个任务,每个任务都在进行一些工作(请求信号量表,等待10 ms并发布信号量)。 我希望此代码只会执行一个任务。但是,由于 dowork 方法的第一个呼叫中的僵局(在等待任务。

有人知道为什么这会导致僵局吗?我尝试使用 threadpool.queueuserworkitem 而不是 task.run thread.sleep 而不是 task> task.delay 这是按预期工作的。但是,一旦我使用任务就停止工作。

这是完整的代码刺:

internal class Program
{
    static int timeoutSec = 60;
    static SemaphoreSlim semaphore = new SemaphoreSlim(1);
    static int numPerIteration = 1000;
    static int iteration = 0;
    static int doneCounter = numPerIteration;
    static int successCount = 0;
    static int failedCount = 0;
    static Stopwatch sw = new Stopwatch();
    static Random rnd = new Random();

    static void Main(string[] args)
    {
        Task.WaitAll(TestUsingTasks());
    }

    static async Task TestUsingTasks()
    {
        while (true)
        {
            var tasks = new List<Task>();
            if (doneCounter >= numPerIteration)
            {
                doneCounter = 0;

                if (iteration >= 1)
                {
                    Log($"+++++ FINISHED TASK ITERATION {iteration} - SUCCESS: {successCount} - FAILURES: {failedCount} - Seconds: {sw.Elapsed.TotalSeconds:F1}", ConsoleColor.Magenta);
                }
                iteration++;

                sw.Restart();
                for (int i = 0; i < numPerIteration; i++)
                {
                    // Start indepdent tasks to do some work
                    Task.Run(async () =>
                    {
                        if (await DoWork())
                        {
                            successCount++;
                        }
                        else
                        {
                            failedCount++;
                        }
                        doneCounter++;
                    });
                }
            }
            await Task.Delay(10);
        }
    }

    static async Task<bool> DoWork()
    {
        if (semaphore.Wait(timeoutSec * 1000)) // Request the semaphore to ensure that one 1 task at a time can enter
        {
            Log($"Got handle for {iteration} within {sw.Elapsed.TotalSeconds:F1}", ConsoleColor.Green);
            var totalSec = sw.Elapsed.TotalSeconds;
            await Task.Delay(10); // Wait for 10ms to simulate some work => Deadlock seems to happen here
            Log($"RELEASING LOCK handle for {iteration} within {sw.Elapsed.TotalSeconds:F1}. WAIT took " + (sw.Elapsed.TotalSeconds - totalSec) + " seconds", ConsoleColor.Gray);
            semaphore.Release();
            return true;
        }
        else
        {
            Log($"ERROR: TASK handle failed for {iteration} within {sw.Elapsed.TotalSeconds:F1} sec", ConsoleColor.Red);
            return false;
        }
    }

    static void Log(string message, ConsoleColor color)
    {
        Console.ForegroundColor = color;
        Console.WriteLine(message);
        Console.ForegroundColor = ConsoleColor.White;
    }
}

预先感谢!

we are running an ASP.NET 6 webapplication and are having strange issues with deadlocks.
The app suddenly freezes after some weeks of operations and it seems that it might be caused by our locking mechanism with the SemaphoreSlim class.

I tried to reproduce the issue with a simple test-project and found something strange.
The following code is simply starting 1000 tasks where each is doing some work (requesting semaphore-handle, waiting for 10 ms and releasing the semaphore).
I expected this code to simply execute one task after another. But it freezes because of a deadlock in the first call of the DoWork method (at await Task.Delay(10)).

Does anyone know why this causes a deadlock? I tried exactly the same code with ThreadPool.QueueUserWorkItem instead of Task.Run and Thread.Sleep instead of Task.Delay and this worked as expected. But as soon as I use the tasks it stops working.

Here is the complete code-snippet:

internal class Program
{
    static int timeoutSec = 60;
    static SemaphoreSlim semaphore = new SemaphoreSlim(1);
    static int numPerIteration = 1000;
    static int iteration = 0;
    static int doneCounter = numPerIteration;
    static int successCount = 0;
    static int failedCount = 0;
    static Stopwatch sw = new Stopwatch();
    static Random rnd = new Random();

    static void Main(string[] args)
    {
        Task.WaitAll(TestUsingTasks());
    }

    static async Task TestUsingTasks()
    {
        while (true)
        {
            var tasks = new List<Task>();
            if (doneCounter >= numPerIteration)
            {
                doneCounter = 0;

                if (iteration >= 1)
                {
                    Log(
quot;+++++ FINISHED TASK ITERATION {iteration} - SUCCESS: {successCount} - FAILURES: {failedCount} - Seconds: {sw.Elapsed.TotalSeconds:F1}", ConsoleColor.Magenta);
                }
                iteration++;

                sw.Restart();
                for (int i = 0; i < numPerIteration; i++)
                {
                    // Start indepdent tasks to do some work
                    Task.Run(async () =>
                    {
                        if (await DoWork())
                        {
                            successCount++;
                        }
                        else
                        {
                            failedCount++;
                        }
                        doneCounter++;
                    });
                }
            }
            await Task.Delay(10);
        }
    }

    static async Task<bool> DoWork()
    {
        if (semaphore.Wait(timeoutSec * 1000)) // Request the semaphore to ensure that one 1 task at a time can enter
        {
            Log(
quot;Got handle for {iteration} within {sw.Elapsed.TotalSeconds:F1}", ConsoleColor.Green);
            var totalSec = sw.Elapsed.TotalSeconds;
            await Task.Delay(10); // Wait for 10ms to simulate some work => Deadlock seems to happen here
            Log(
quot;RELEASING LOCK handle for {iteration} within {sw.Elapsed.TotalSeconds:F1}. WAIT took " + (sw.Elapsed.TotalSeconds - totalSec) + " seconds", ConsoleColor.Gray);
            semaphore.Release();
            return true;
        }
        else
        {
            Log(
quot;ERROR: TASK handle failed for {iteration} within {sw.Elapsed.TotalSeconds:F1} sec", ConsoleColor.Red);
            return false;
        }
    }

    static void Log(string message, ConsoleColor color)
    {
        Console.ForegroundColor = color;
        Console.WriteLine(message);
        Console.ForegroundColor = ConsoleColor.White;
    }
}

Thanks in advance!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

醉梦枕江山 2025-02-15 23:20:52

,但由于Dowork方法的第一个呼吁中的僵局(在等待任务)。

我认为这不是僵局,而是a 问题。如果您等待足够长的时间,您将看到线程能够不时完成模拟等待。

此处的快速修复是使用非块 waitasync 等待来调用:

static async Task<bool> DoWork()
{
    if (await semaphore.WaitAsync(timeoutSec * 1000))
    {
         ...
    }
}

还要注意:

  • 建议在 Wait .. 中包装代码。 试用阻止并在最后发布信号量
  • 在平行环境中的递增计数器应该以原子方式进行,例如用 互锁。Increment

But it freezes because of a deadlock in the first call of the DoWork method (at await Task.Delay(10)).

I would argue that it is not deadlock but a thread starvation issue. If you wait long enough you will see that threads will be able to finish the simulation wait from time to time.

The quick fix here is using non-blocking WaitAsync call with await:

static async Task<bool> DoWork()
{
    if (await semaphore.WaitAsync(timeoutSec * 1000))
    {
         ...
    }
}

Also note:

  • It is recommended to wrap the code after Wait.. into try-finally block and release the semaphore in the finally.
  • Incrementing counters in parallel environments better should be done in atomic fashion, for example with Interlocked.Increment.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文