Home C# Tutorial on Thread.Join in C#

Tutorial on Thread.Join in C#

The Thread.Join method in C# is used to block the calling thread until a specified thread has finished executing.

This is useful when you need to ensure that a particular thread has completed its work before continuing with the next steps in your program.

Thread.Join is particularly helpful for coordinating thread execution in multithreaded applications.

In this tutorial, we’ll cover:

1. Using Thread.Join to Wait for a Thread to Complete

When you call Thread.Join on a thread, it causes the calling thread to wait until the target thread finishes. Here’s a basic example:

using System;
using System.Threading;

class Program {
    static void Main() {
        Thread thread = new Thread(() => {
            Console.WriteLine("Thread started.");
            Thread.Sleep(2000); // Simulate some work for 2 seconds
            Console.WriteLine("Thread finished.");
        });

        thread.Start();
        Console.WriteLine("Waiting for thread to complete...");

        thread.Join(); // Wait until the thread completes

        Console.WriteLine("Main thread resuming after thread has completed.");
    }
}

Output:

Thread started.
Waiting for thread to complete...
# 2-second pause
Thread finished.
Main thread resuming after thread has completed.

In this example:

  • thread.Join() makes the main thread wait until the thread has completed its work.
  • The main thread resumes execution only after thread finishes.

2. Using Thread.Join with a Timeout

You can specify a timeout for Thread.Join, which causes it to wait for a specified amount of time. If the thread finishes within that time, Join returns true; if the time elapses, Join returns false, allowing the calling thread to continue without waiting indefinitely.

Example: Using Thread.Join with a Timeout in Milliseconds

using System;
using System.Threading;

class Program {
    static void Main() {
        Thread thread = new Thread(() => {
            Console.WriteLine("Thread started.");
            Thread.Sleep(3000); // Simulate work for 3 seconds
            Console.WriteLine("Thread finished.");
        });

        thread.Start();
        Console.WriteLine("Waiting for thread to complete (2-second timeout)...");

        bool completed = thread.Join(2000); // Wait for up to 2 seconds

        if (completed) {
            Console.WriteLine("Thread completed within 2 seconds.");
        } else {
            Console.WriteLine("Thread did not complete within 2 seconds.");
        }

        Console.WriteLine("Main thread resuming.");
    }
}

Output:

Thread started.
Waiting for thread to complete (2-second timeout)...
# 2-second pause
Thread did not complete within 2 seconds.
Main thread resuming.
Thread finished.

In this example:

  • thread.Join(2000) waits up to 2 seconds for the thread to complete.
  • Since the thread’s work takes 3 seconds, Join times out, and the main thread resumes without waiting for thread to finish.

Example: Using Thread.Join with a Timeout in TimeSpan

You can also use a TimeSpan to specify the timeout.

using System;
using System.Threading;

class Program {
    static void Main() {
        Thread thread = new Thread(() => {
            Console.WriteLine("Thread started.");
            Thread.Sleep(4000); // Simulate work for 4 seconds
            Console.WriteLine("Thread finished.");
        });

        thread.Start();
        Console.WriteLine("Waiting for thread to complete (3-second timeout)...");

        bool completed = thread.Join(TimeSpan.FromSeconds(3)); // Wait for up to 3 seconds

        if (completed) {
            Console.WriteLine("Thread completed within 3 seconds.");
        } else {
            Console.WriteLine("Thread did not complete within 3 seconds.");
        }

        Console.WriteLine("Main thread resuming.");
    }
}

Output:

Thread started.
Waiting for thread to complete (3-second timeout)...
# 3-second pause
Thread did not complete within 3 seconds.
Main thread resuming.
Thread finished.

In this example:

  • thread.Join(TimeSpan.FromSeconds(3)) waits up to 3 seconds for the thread to complete.
  • Since the thread’s work takes 4 seconds, Join times out after 3 seconds, allowing the main thread to resume.

3. Practical Examples of Thread.Join in Real-World Scenarios

Example 1: Sequential Task Execution

Thread.Join can be used to ensure that tasks run in a specific sequence, even if they are in separate threads.

using System;
using System.Threading;

class Program {
    static void Task1() {
        Console.WriteLine("Task 1 started.");
        Thread.Sleep(2000);
        Console.WriteLine("Task 1 completed.");
    }

    static void Task2() {
        Console.WriteLine("Task 2 started.");
        Thread.Sleep(1000);
        Console.WriteLine("Task 2 completed.");
    }

    static void Main() {
        Thread thread1 = new Thread(Task1);
        Thread thread2 = new Thread(Task2);

        thread1.Start();
        thread1.Join(); // Wait for Task 1 to complete

        thread2.Start();
        thread2.Join(); // Wait for Task 2 to complete

        Console.WriteLine("Both tasks have completed in sequence.");
    }
}

Output:

Task 1 started.
# 2-second pause
Task 1 completed.
Task 2 started.
# 1-second pause
Task 2 completed.
Both tasks have completed in sequence.

In this example:

  • thread1.Join() waits for Task1 to finish before starting Task2.
  • thread2.Join() ensures Task2 completes before the program proceeds.

Example 2: Waiting for Multiple Threads to Finish

You can use multiple Join calls to wait for multiple threads to complete before moving forward.

using System;
using System.Threading;

class Program {
    static void Worker(int id) {
        Console.WriteLine($"Worker {id} started.");
        Thread.Sleep(id * 1000); // Each thread sleeps for different durations
        Console.WriteLine($"Worker {id} completed.");
    }

    static void Main() {
        Thread thread1 = new Thread(() => Worker(1));
        Thread thread2 = new Thread(() => Worker(2));
        Thread thread3 = new Thread(() => Worker(3));

        thread1.Start();
        thread2.Start();
        thread3.Start();

        Console.WriteLine("Waiting for all worker threads to complete...");

        thread1.Join();
        thread2.Join();
        thread3.Join();

        Console.WriteLine("All worker threads have completed.");
    }
}

Output:

Worker 1 started.
Worker 2 started.
Worker 3 started.
# Worker 1 finishes after 1 second
Worker 1 completed.
# Worker 2 finishes after 2 seconds
Worker 2 completed.
# Worker 3 finishes after 3 seconds
Worker 3 completed.
All worker threads have completed.

In this example:

  • Join is used to ensure that the main thread waits for all three worker threads to complete before printing the final message.

Example 3: Avoiding Incomplete Output in File Writing

Using Thread.Join, you can ensure that a thread writing data to a file has completed its work before reading the file.

using System;
using System.IO;
using System.Threading;

class Program {
    static void WriteToFile() {
        using (StreamWriter writer = new StreamWriter("output.txt")) {
            writer.WriteLine("Writing data to file...");
            Thread.Sleep(2000); // Simulate file writing delay
            writer.WriteLine("Data write complete.");
        }
        Console.WriteLine("File writing thread finished.");
    }

    static void Main() {
        Thread fileThread = new Thread(WriteToFile);

        fileThread.Start();
        Console.WriteLine("Waiting for file writing to complete...");

        fileThread.Join(); // Wait until file writing is complete

        Console.WriteLine("Reading file contents:");
        string content = File.ReadAllText("output.txt");
        Console.WriteLine(content);
    }
}

Output:

Waiting for file writing to complete...
File writing thread finished.
Reading file contents:
Writing data to file...
Data write complete.

In this example:

  • fileThread.Join() ensures the main thread waits until the file writing is complete before attempting to read the file.

Summary

In this tutorial, we covered the Thread.Join method in C# and demonstrated its usage in various scenarios:

  1. Using Thread.Join to Wait for a Thread to Complete: Ensuring that a thread finishes before the calling thread proceeds.
  2. Using Thread.Join with a Timeout: Specifying a time limit to wait for a thread, allowing the calling thread to continue if the timeout elapses.
  3. Practical Examples:
    • Sequential task execution.
    • Waiting for multiple threads to finish.
    • Ensuring file writing completes before reading the file.

The Thread.Join method is a powerful tool in C# for synchronizing threads, allowing you to control when threads complete before proceeding with other tasks. For applications where precise timing or sequential execution is required, Thread.Join is an essential method to ensure that operations occur in the correct order.

You may also like