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

C# Task.WhenAll()一次只执行2个线程?,c#,caching,locking,task,semaphore,C#,Caching,Locking,Task,Semaphore,在这个问题中,我试图缓存一个值,我们称之为foo。如果未缓存该值,则需要一段时间来检索 我的问题不是实施it,而是测试it 为了测试它,我使用Task.WhenAll()同时启动了5个任务来获取缓存值。第一个线程进入锁并异步检索值,而其他4个线程应等待锁。在等待之后,他们应该逐个地重新检查缓存的值,发现缓存它的第一个线程已经检索到它,并返回它而不进行第二次检索 [TestClass] public class Class2 { private readonly Semaphore sem

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

我的问题不是实施it,而是测试it

为了测试它,我使用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);
    }
}
然而,您可以从测试的输出中看到,它并不像我期望的那样。看起来好像只有两个线程同时执行,而其他线程则等到前两个线程完成后才进入GetFoo()方法

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

任务。whalll()
不启动任务-它只是等待它们

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

  • 您等待尚未完成的操作,并且您的同步上下文在新线程上安排继续(例如,在WinForms上下文中不会这样做;它只会重用UI线程)
  • 您显式地使用了
    Task.Run
    ,任务调度器将创建一个新线程来运行它。(当然,它可能不需要这样做。)
  • 显式启动一个新线程
老实说,在异步方法中使用blocking
Semaphore
方法对我来说是非常错误的。您似乎没有真正接受异步的概念。。。我还没有尝试准确地分析您的代码将要做什么,但我认为您需要阅读更多关于
async
如何工作以及如何最好地使用它的内容

这应该允许运行其他任务,但我怀疑它们在以下位置被阻止:

混合并发样式(在本例中,使用任务和手动控制与同步对象)是非常困难的

(您似乎试图通过将多个并发任务全部池化来获得相同的价值:这似乎有些过分。)


默认情况下,.NET将
任务的并发性限制为可用的(逻辑)CPU内核数,我怀疑您的系统有两个。

您的问题似乎在于
信号量.WaitOne()

async
方法将同步运行,直到到达其第一个
wait
。在您的代码中,第一个
wait
仅在发出
WaitOne
信号之后。一个方法是
async
这一事实当然并不意味着它在多个线程上运行,它通常意味着相反的结果

一定要避免这种情况,使用这种方式调用线程将产生控制,直到信号量发出完成信号

public class Class2
{
    private readonly SemaphoreSlim semaphore = new SemaphoreSlim(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
       {
           await semaphore.WaitAsync();
           {
               // 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;
}
公共类2
{
私有只读信号量slim信号量=新信号量slim(1,1);
私人布尔福;
私有异步任务GetFoo()
{
布尔值;
//获取当前foo的原子操作
bool?currentFoo=this.foo;
if(currentFoo.HasValue)
{
Console.WriteLine(“Foo已检索”);
fooValue=当前Foo.Value;
}
其他的
{
wait semaphore.WaitAsync();
{
//获取当前foo的原子操作
currentFoo=this.foo;
if(currentFoo.HasValue)
{
//Foo在等待时被检索到
Console.WriteLine(“等待时检索到Foo”);
fooValue=当前Foo.Value;
}
其他的
{
//模拟等待获取foo值
Console.WriteLine(“获取新foo”);
等待任务延迟(时间跨度从秒(5));
this.foo=true;
fooValue=true;
}
}
semaphore.Release();
}
返回值;
}

这(调用异步方法实际上并不强制并行化)对我来说是有意义的…我使用信号量来解决无法在锁()中使用wait的问题块,我知道这是一件坏事,因为可能会出现死锁。我将研究其他解决方案。但这至少向我解释了测试没有按预期执行的原因。如果实现正确,测试可能会正常工作。那么在锁()中使用wait的首选模型是什么?@user3339997:看到你将johns的答案标记为解决方案,它没有为你的问题提供解决方案。我认为他的答案稍微正确一些,因为它不涉及在信号量中使用等待。他提供了一个链接,指向如何在等待中正确锁定的答案,并提供了关于我问题的更多背景。你的答案是我们他提供了一个指向
AsyncLock
的链接,这与等待
SemaphoreSlim
没有什么不同。他在回答中谈到不要使用阻塞信号量。无论如何,你可以自由选择你想要的:)
 await Task.Delay(TimeSpan.FromSeconds(5));
semaphore.WaitOne();
public class Class2
{
    private readonly SemaphoreSlim semaphore = new SemaphoreSlim(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
       {
           await semaphore.WaitAsync();
           {
               // 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;
}