Search code examples
task-parallel-librarytaskwait

Task.WaitAny degenerate case behaviour


I can't find any documentation for the behaviour of Task.WaitAny() under the following scenarios:

  1. Given an empty array of tasks.
  2. Given an array of tasks, of which one has already completed.
  3. Given an array of tasks, all of which have already completed.

My assumption would be that 2 & 3 would return immediately (#3 picking an arbitrary index to return, likely the first or last one depending on implementation detail but not guaranteed.) and I'd sorta expect 1 to return true?


Solution

  • Some brief unit tests seem to confirm this... the following all pass. If anyone can find some formal documentation of this behaviour, I'd be glad to see it. (Even if only to improve my ability to locate it myself next time!)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using NUnit.Framework;
    using System.Threading;
    
    namespace TANTest.Threading
    {
      [TestFixture]
      public class MetaTaskTests
      {
        private const int SafeTimeOutMillisecs = 1000;
    
        [Test, Timeout(SafeTimeOutMillisecs)]
        public void Meta_NewTaskCompletes()
        {
          var task = new Task(() => { });
          task.Start();
          Thread.Sleep(10);
    
          Assert.That(task.IsCompleted, Is.True);
        }
    
        [Test, Timeout(SafeTimeOutMillisecs)]
        public void Meta_EmptySetOfTasks_WaitsProceed()
        {
          var tasks = new List<Task>();
          Task.WaitAny(tasks.ToArray());
          Task.WaitAll(tasks.ToArray());
        }
    
        [Test, Timeout(SafeTimeOutMillisecs)]
        public void Meta_EmptySetOfTasks_WaitsGiveExpectedOutputs()
        {
          var tasks = new List<Task>();
          Assert.That(Task.WaitAny(tasks.ToArray(), TimeSpan.FromMilliseconds(100)), Is.EqualTo(-1));
          Assert.That(Task.WaitAll(tasks.ToArray(), TimeSpan.FromMilliseconds(100)), Is.True);
        }
    
        [Test, Timeout(SafeTimeOutMillisecs)]
        public void Meta_SetOfCompletedTasks_WaitsProceed()
        {
          var tasks = new List<Task>
          {
            new Task(() => { }),
            new Task(() => { })
          };
    
          tasks.ForEach(task => task.Start());
          Thread.Sleep(10);
    
          Task.WaitAny(tasks.ToArray());
          Task.WaitAll(tasks.ToArray());
        }
    
        [Test, Timeout(SafeTimeOutMillisecs)]
        public void Meta_SetOfCompletedTasks_WaitsGiveExpectedOutputs()
        {
          var tasks = new List<Task>
          {
            new Task(() => { }),
            new Task(() => { })
          };
    
          tasks.ForEach(task => task.Start());
          Thread.Sleep(10);
    
          Assert.That(Task.WaitAny(tasks.ToArray(), TimeSpan.FromMilliseconds(100)), Is.GreaterThan(-1));
          Assert.That(Task.WaitAll(tasks.ToArray(), TimeSpan.FromMilliseconds(100)), Is.True);
        }
    
        [Test, Timeout(SafeTimeOutMillisecs)]
        public void Meta_SetOfUnstartedTasks_WaitsDontProceed()
        {
          var tasks = new List<Task>
          {
            new Task(() => { }),
            new Task(() => { })
          };
    
          Thread.Sleep(10);
    
          Assert.That(Task.WaitAny(tasks.ToArray(), TimeSpan.FromMilliseconds(100)), Is.EqualTo(-1));
          Assert.That(Task.WaitAll(tasks.ToArray(), TimeSpan.FromMilliseconds(100)), Is.False);
        }
    
    
        [Test, Timeout(SafeTimeOutMillisecs)]
        public void Meta_SetOfTasksWithOneCompleted_AnyProceedsAndOutputsIndex()
        {
          var tasks = new List<Task>
          {
            new Task(() => { }),
            new Task(() => { })
          };
    
          tasks.First().Start();
          Thread.Sleep(10);
    
          Assert.That(Task.WaitAny(tasks.ToArray()), Is.GreaterThan(-1));
        }
    
        [Test, Timeout(SafeTimeOutMillisecs)]
        public void Meta_SetOfTasksWithOneCompleted_AllTimesOutWithExpectedOutput()
        {
          var tasks = new List<Task>
          {
            new Task(() => { }),
            new Task(() => { })
          };
    
          tasks.First().Start();
          Thread.Sleep(10);
    
          Assert.That(Task.WaitAll(tasks.ToArray(), TimeSpan.FromMilliseconds(100)), Is.False);
        }
      }
    }