Wait for ANY thread to finish, not ALL

2019-06-25 01:05发布

问题:

I'm starting multiple threads and would like to know when any of then finishes. I know the following code:

foreach (Thread t in threads)
    t.Join();

But it will only wait for all threads together. That's much too late. I need to know when one thread finishes, even when other threads are still running. I'm looking for something equivalent to WaitAny only for threads. But I can't add code to all threads I'm monitoring, so using signals or other synchronisation objects is not an option.

Some clarification: I'm working on a logging/tracing tool that should log the application's activity. I can insert log statements when a thread starts, but I can't insert a log statement on every possible way out of the thread (multiple exit points, exceptions etc.). So I'd like to register the new thread and then be notified when it finishes to write a log entry. I could asynchronously Join on every thread, but that means a second thread for every monitored thread which may seem a bit much overhead. Threads are used by various means, be it a BackgroundWorker, Task or pool thread. In its essence, it's a thread and I'd like to know when it's done. The exact thread mechanism is defined by the application, not the logging solution.

回答1:

In my opinion WaitHandle.WaitAny is the best solution, since you don't like to use it for some xyz reason you can try something like this.

Take the advantage of Thread.Join(int) method which takes millisecond timeout and returns true when thread is terminated or false when timed out.

List<Thread> threads = new List<Thread>();

while (!threads.Any(x=> x.Join(100)))
{

}

You can alter the timeout of Join If you know how long it will take.



回答2:

Instead of Threads use Tasks. It has the method WaitAny.

Task.WaitAny

As you can read here,

  • More efficient and more scalable use of system resources.
  • More programmatic control than is possible with a thread or work item.


回答3:

My answer is based on your clarification that all you have is Thread.Current. Disclaimer: IMO, what you're trying to do is a hack, thus my idea by all means is a hack too.

So, use reflection to obtain the set of native Win32 handles for your desired threads. Your are looking for Thread.GetNativeHandle method which is internal, so you call it like thread.GetType().InvokeMember("GetNativeHandle", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic, ...). Use a reflection tool of your choice or Framework sources to learn more about it. Once you've got the handles, go on with one of the following options:

  • Set up your own implementation of SynchronizationContext (derive from it) and use SynchronizationContext.WaitHelper(waitAll: false) to wait for your unmanaged handles.

  • Use the raw Win32 API like WaitForMultipleObjects or CoWaitForMultipleObjects (depending on whether you need to pump messages).

Perform the wait on a separate child or pool thread.

[EDITED] Depending on the execution environment of your target threads, this hack may not work, because one-to-one mapping between managed and unmanaged threads is not guaranteed:

It is possible to determine the Windows thread that is executing the code for a managed thread and to retrieve its handle. However, it still doesn't make sense to call the SetThreadAffinityMask function for this Windows thread, because the managed scheduler can continue the execution of a managed thread in another Windows thread.

It appears however, this may be an implication only for custom CLR hosts. Also, it appears to be possible to control managed thread affinity with Thread.BeginThreadAffinity and Thread.EndThreadAffinity.



回答4:

You could use a background worker for your working threads.

Then hook all the RunWorkerCompleted events to a method that will wait for them.

If you want that to be synched to the code where you're currently waiting for the join, then the problem is reduced to just synchronizing that single event method to that place in code.

Better yet, I'd suggest to do what you're doing asynchronously without blocking, and just do what you want in the event.



回答5:

Would you consider wrapping your thread invocations with another 'logging' thread? That way you could log synchronously before & after the thread run.

Something like this pseudo-code:

int threadLogger(<parms>) {
    log("starting thread");
    retcode = ActualThreadBody(<parms>);
    log("exiting thread");
    return retcode;
}

If you have more information on the thread started, you could log that as well. You could also take the thread function as a parameter in the case where you have multiple types of threads to start, which it sounds like you do.