Search code examples
c#multithreadingsta

Is it possible to await a Thread in C#?


I am in a situation where I have to spawn a new thread manually, so I am able to can call .SetApartmentState(ApartmentState.STA). This means (as far as I know) that I cannot use Task. But I would like to know when the thread was done running, something like the await which works with async. However, the best I can come up with is a loop, constantly checking Thread.IsAlive, like this:

var thread = new Thread(() =>
{ 
    // my code here 
});

thread.SetApartmentState(ApartmentState.STA);
thread.Start();

while(thread.IsAlive)
{
    // Wait 100 ms
    Thread.Sleep(100);
}

This should work (as long as the thread don't end up stalling), but it seems kind of clumsy. Isn't there a more clever way to check when the thread is done (or dead)?

It is only to avoid blocking the GUI thread, so any minor performance hits are fine (like some hundred milliseconds).


Solution

  • Here is an extension method you could use to enable the awaiting of threads (inspired from this article: await anything).

    public static TaskAwaiter GetAwaiter(this Thread thread)
    {
        ArgumentNullException.ThrowIfNull(thread);
        return Task.Run(async () =>
        {
            using PeriodicTimer timer = new(TimeSpan.FromMilliseconds(100));
            while (thread.IsAlive) await timer.WaitForNextTickAsync();
            thread.Join(); // Let's be extra sure that the thread has finished
        }).GetAwaiter();
    }
    

    This implementation pools the IsAlive property of the thread every 100 milliseconds, using a PeriodicTimer (.NET 6). The implication is that the awaiting is not completely passive (there is some overhead), and the completion of the awaitable is not instantaneous. The gap between the termination of the thread and the completion of the awaitable will normally be under 100 milliseconds, but it could grow larger in case the ThreadPool is saturated (the PeriodicTimer relies on the ThreadPool for ticking its events).

    Usage example:

    Thread thread = new(() =>
    { 
        Thread.Sleep(1000); // Simulate some background work
    });
    thread.IsBackground = true;
    thread.Start();
    await thread; // Wait asynchronously until the thread is completed
    

    For a GetAwaiter version that doesn't depend on the PeriodicTimer class, and so it can be used with .NET versions earlier than .NET 6, see the 3rd revision of this answer.