70

I'm running into a common pattern in the code that I'm writing, where I need to wait for all threads in a group to complete, with a timeout. The timeout is supposed to be the time required for all threads to complete, so simply doing Thread.Join(timeout) for each thread won't work, since the possible timeout is then timeout * numThreads.

Right now I do something like the following:

var threadFinishEvents = new List<EventWaitHandle>();

foreach (DataObject data in dataList)
{
    // Create local variables for the thread delegate
    var threadFinish = new EventWaitHandle(false, EventResetMode.ManualReset);
    threadFinishEvents.Add(threadFinish);

    var localData = (DataObject) data.Clone();
    var thread = new Thread(
        delegate()
        {
            DoThreadStuff(localData);
            threadFinish.Set();
        }
    );
    thread.Start();
}

Mutex.WaitAll(threadFinishEvents.ToArray(), timeout);

However, it seems like there should be a simpler idiom for this sort of thing.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
JSBձոգչ
  • 40,684
  • 18
  • 101
  • 169
  • any final solution with full source code working ? maybe more complex sample for notify errors in each thread and after WaitAll shows a summary ? – Kiquenet Jan 01 '14 at 10:11
  • That's what I would do - can't think of a simpler way to do that. – Guy Nov 04 '08 at 19:40

10 Answers10

29

I still think using Join is simpler. Record the expected completion time (as Now+timeout), then, in a loop, do

if(!thread.Join(End-now))
    throw new NotFinishedInTime();
Martin v. Löwis
  • 124,830
  • 17
  • 198
  • 235
21

With .NET 4.0 I find System.Threading.Tasks a lot easier to work with. Here's spin-wait loop which works reliably for me. It blocks the main thread until all the tasks complete. There's also Task.WaitAll, but that hasn't always worked for me.

        for (int i = 0; i < N; i++)
        {
            tasks[i] = Task.Factory.StartNew(() =>
            {               
                 DoThreadStuff(localData);
            });
        }
        while (tasks.Any(t => !t.IsCompleted)) { } //spin wait
T. Webster
  • 9,605
  • 6
  • 67
  • 94
  • 45
    You want `Task.WaitAll(tasks)` instead of that spin. – ladenedge Dec 26 '11 at 17:27
  • 13
    @T.Webster - what do you mean by saying that `Task.WaitAll` didn't always work for you? Are you suggesting that this methods has bugs? Can you provide examples. It always works for me. On the other side, the spin that you suggest is quite expensive thing to do. You are hammering your CPU. `Task.WaitAll` is much better solution. – Alex Aza Dec 03 '12 at 08:43
  • 2
    If you use this option I would suggest at least putting `Thread.Yield()` in the while body. – Wouter Simons Feb 06 '13 at 12:55
  • any more complex sample for notify errors in each thread and after WaitAll shows a summary ? – Kiquenet Jan 01 '14 at 10:12
  • 4
    Sorry, -1 for spinwait. Motivation "Task.WaitAll hasn't always worked for me" without more detail is not a valid excuse for doing this. – erikkallen Apr 21 '15 at 07:39
  • 1
    I have experienced that WaitAll isn't always working correctly in 4.0. Maybe it is a problem in tasks in itself. I usually want a result recorded on the task and experienced that quite often one task never ends up written the result (last thing in task.) It is either all or one less. Not two, three, or N missing. Always just one when it happens. I did try a different spin option to check if the number of results matched number of tasks, and it never got out of that loop. The task just disappeared before completing even though WaitAll was released. – Quintium Apr 23 '15 at 14:16
  • @erikkallen constructive criticism is welcome +1. What more details are you asking me to add? What solution would you use to solve this problem? – T. Webster May 19 '15 at 03:46
  • @AlexAza, sorry I haven't reviewed the comments since you asked your question. It was 2011 when I wrote this answer, and using Visual Studio 2015 .NET 4.0, I'll try very soon to edit my answer to with an example reproducing what I meant by saying that 'Task.WaitAll' didn't always work. – T. Webster May 19 '15 at 04:54
  • @Quintium and how did the spinwait loop of my answer work for you - was it reliable, or did you run into problems? – T. Webster May 19 '15 at 04:56
  • @T.Webster In order to find this answer satisfying I need information of when the built-in method Task.WaitAll does not work. Most likely that method is not broken but you are doing something wrong. – erikkallen May 19 '15 at 08:39
  • That spin wait would grind my CPU to a halt in certain situations. Didn't happen consistently so was quite difficult to track the problem down. – Top Cat Feb 26 '18 at 17:05
11

This doesn't answer the question (no timeout), but I've made a very simple extension method to wait all threads of a collection:

using System.Collections.Generic;
using System.Threading;
namespace Extensions
{
    public static class ThreadExtension
    {
        public static void WaitAll(this IEnumerable<Thread> threads)
        {
            if(threads!=null)
            {
                foreach(Thread thread in threads)
                { thread.Join(); }
            }
        }
    }
}

Then you simply call:

List<Thread> threads=new List<Thread>();
//Add your threads to this collection
threads.WaitAll();
Vincent
  • 938
  • 13
  • 20
10

Since the question got bumped I will go ahead and post my solution.

using (var finished = new CountdownEvent(1)) 
{ 
  for (DataObject data in dataList) 
  {   
    finished.AddCount();
    var localData = (DataObject)data.Clone(); 
    var thread = new Thread( 
        delegate() 
        {
          try
          {
            DoThreadStuff(localData); 
            threadFinish.Set();
          }
          finally
          {
            finished.Signal();
          }
        } 
    ); 
    thread.Start(); 
  }  
  finished.Signal(); 
  finished.Wait(YOUR_TIMEOUT); 
} 
Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
9

Off the top of my head, why don't you just Thread.Join(timeout) and remove the time it took to join from the total timeout?

// pseudo-c#:

TimeSpan timeout = timeoutPerThread * threads.Count();

foreach (Thread thread in threads)
{
    DateTime start = DateTime.Now;

    if (!thread.Join(timeout))
        throw new TimeoutException();

    timeout -= (DateTime.Now - start);
}

Edit: code is now less pseudo. don't understand why you would mod an answer -2 when the answer you modded +4 is exactly the same, only less detailed.

Omer van Kloeten
  • 11,800
  • 9
  • 42
  • 53
  • That's incorrect: the complete timeout for all threads is given, any any thread (including the first one being waited for) may consume the complete timeout. Your code doesn't check the result of the Join. – Martin v. Löwis Nov 04 '08 at 19:49
  • 3
    First of all, this is pseudo-code and is, as it is not real code, incomplete. I assumed a time equal to or less than zero will throw or something. Secondly, if the first thread consumes the entire time, the operation should time out as a whole. This is correct according to the question. – Omer van Kloeten Nov 04 '08 at 20:44
7

This may not be an option for you, but if you can use the Parallel Extension for .NET then you could use Tasks instead of raw threads and then use Task.WaitAll() to wait for them to complete.

Jon Norton
  • 2,969
  • 21
  • 20
1

I read the book C# 4.0: The Complete Reference of Herbert Schildt. The author use join to give a solution :

class MyThread
    {
        public int Count;
        public Thread Thrd;
        public MyThread(string name)
        {
            Count = 0;
            Thrd = new Thread(this.Run);
            Thrd.Name = name;
            Thrd.Start();
        }
        // Entry point of thread.
        void Run()
        {
            Console.WriteLine(Thrd.Name + " starting.");
            do
            {
                Thread.Sleep(500);
                Console.WriteLine("In " + Thrd.Name +
                ", Count is " + Count);
                Count++;
            } while (Count < 10);
            Console.WriteLine(Thrd.Name + " terminating.");
        }
    }
    // Use Join() to wait for threads to end.
    class JoinThreads
    {
        static void Main()
        {
            Console.WriteLine("Main thread starting.");
            // Construct three threads.
            MyThread mt1 = new MyThread("Child #1");
            MyThread mt2 = new MyThread("Child #2");
            MyThread mt3 = new MyThread("Child #3");
            mt1.Thrd.Join();
            Console.WriteLine("Child #1 joined.");
            mt2.Thrd.Join();
            Console.WriteLine("Child #2 joined.");
            mt3.Thrd.Join();
            Console.WriteLine("Child #3 joined.");
            Console.WriteLine("Main thread ending.");
            Console.ReadKey();
        }
    }
0

Possible solution:

var tasks = dataList
    .Select(data => Task.Factory.StartNew(arg => DoThreadStuff(data), TaskContinuationOptions.LongRunning | TaskContinuationOptions.PreferFairness))
    .ToArray();

var timeout = TimeSpan.FromMinutes(1);
Task.WaitAll(tasks, timeout);

Assuming dataList is the list of items and each item needs to be processed in a separate thread.

Alex Aza
  • 76,499
  • 26
  • 155
  • 134
0

I was tying to figure out how to do this but i could not get any answers from google. I know this is an old thread but here was my solution:

Use the following class:

class ThreadWaiter
    {
        private int _numThreads = 0;
        private int _spinTime;

        public ThreadWaiter(int SpinTime)
        {
            this._spinTime = SpinTime;
        }

        public void AddThreads(int numThreads)
        {
            _numThreads += numThreads;
        }

        public void RemoveThread()
        {
            if (_numThreads > 0)
            {
                _numThreads--;
            }
        }

        public void Wait()
        {
            while (_numThreads != 0)
            {
                System.Threading.Thread.Sleep(_spinTime);
            }
        }
    }
  1. Call Addthreads(int numThreads) before executing a thread(s).
  2. Call RemoveThread() after each one has completed.
  3. Use Wait() at the point that you want to wait for all the threads to complete before continuing
mauris
  • 42,982
  • 15
  • 99
  • 131
Dylan
  • 9
  • 1
0

Here is an implementation inspired by Martin v. Löwis's answer:

/// <summary>
/// Blocks the calling thread until all threads terminate, or the specified
/// time elapses. Returns true if all threads terminated in time, or false if
/// at least one thread has not terminated after the specified amount of time
/// elapsed.
/// </summary>
public static bool JoinAll(IEnumerable<Thread> threads, TimeSpan timeout)
{
    ArgumentNullException.ThrowIfNull(threads);
    if (timeout < TimeSpan.Zero)
        throw new ArgumentOutOfRangeException(nameof(timeout));

    Stopwatch stopwatch = Stopwatch.StartNew();
    foreach (Thread thread in threads)
    {
        if (!thread.IsAlive) continue;
        TimeSpan remaining = timeout - stopwatch.Elapsed;
        if (remaining < TimeSpan.Zero) return false;
        if (!thread.Join(remaining)) return false;
    }
    return true;
}

For measuring the remaining time, instead of the DateTime.Now it uses a Stopwatch. The Stopwatch component is not sensitive to system-wide clock adjustments.

Usage example:

bool allTerminated = JoinAll(new[] { thread1, thread2 }, TimeSpan.FromSeconds(10));

The timeout must be a positive or zero TimeSpan. The Timeout.InfiniteTimeSpan constant is not supported.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104