Task.WhenAll()一次只执行2个线程?

Rya*_*dge 4 c# caching semaphore locking task

在这个问题中,我试图缓存单个值,让我们称之为foo.如果未缓存该值,则需要一段时间才能检索.

我的问题不是实现它,而是测试它.

为了测试它,我使用Task.WhenAll()来启动5个同步任务来获取缓存值.第一个进入锁并以异步方式检索值,而其他4个线程等待锁.在等待之后,他们应该逐个重新检查缓存的值,发现它已被缓存它的第一个线程检索到,并在没有第二次检索的情况下返回它.

[TestClass]
public class Class2
{
    private readonly Semaphore semaphore = new Semaphore(1, 1);

    private bool? foo;

    private async Task<bool> GetFoo()
    {
        bool fooValue;

        // Atomic operation to get current foo
        bool? currentFoo = this.foo;

        if (currentFoo.HasValue)
        {
            Console.WriteLine("Foo already retrieved");
            fooValue = currentFoo.Value;
        }
        else
        {
            semaphore.WaitOne();
            {
                // Atomic operation to get current foo
                currentFoo = this.foo;

                if (currentFoo.HasValue)
                {
                    // Foo was retrieved while waiting
                    Console.WriteLine("Foo retrieved while waiting");
                    fooValue = currentFoo.Value;
                }
                else
                {
                    // Simulate waiting to get foo value
                    Console.WriteLine("Getting new foo");
                    await Task.Delay(TimeSpan.FromSeconds(5));
                    this.foo = true;
                    fooValue = true;
                }
            }
            semaphore.Release();
        }

        return fooValue;
    }

    [TestMethod]
    public async Task Test()
    {
        Task[] getFooTasks = new[] {
            this.GetFoo(),
            this.GetFoo(),
            this.GetFoo(),
            this.GetFoo(),
            this.GetFoo(),
        };
        await Task.WhenAll(getFooTasks);
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的实际测试和生产代码中,我通过接口检索值并使用Moq模拟该接口.在测试结束时,我验证接口仅被调用1次(通过),而不是> 1次(失败).

输出:

Getting new foo
Foo retrieved while waiting
Foo already retrieved
Foo already retrieved
Foo already retrieved
Run Code Online (Sandbox Code Playgroud)

但是你可以从测试的输出中看出它并不像我预期的那样.看起来好像只有2个线程同时执行,而其他线程等到前两个线程完成甚至进入 GetFoo()方法.

为什么会这样?是因为我在VS单元测试中运行吗?请注意,我的测试仍然通过,但不是我预期的方式.我怀疑VS单元测试中的线程数有一些限制.

Jon*_*eet 5

Task.WhenAll()不会启动任务 - 它只是等待它们.

同样,调用async方法实际上并不强制并行化 - 它不会引入新线程或类似的东西.在以下情况下,您只能获得新线

  • 等待尚未完成的事情,并且您的同步上下文在新线程上调度延续(例如,它不会在WinForms上下文中执行;它只是重用UI线程)
  • 您明确使用Task.Run,并且任务计划程序创建一个新线程来运行它.(当然,这可能不需要.)
  • 您明确地开始一个新线程.

说实话,在异步方法中使用阻塞 Semaphore方法对我来说是非常错误的.你似乎并没有真正接受异步的想法...我没有试图准确分析你的代码将要做什么,但我认为你需要阅读更多关于如何async工作,以及如何最好地使用它.