关于多线程:C#,我如何知道所有线程何时完成?

C#, how do I know when all my theads have finished?

我有一个"线程"类,它会抛出执行进程的线程。 线程在threader类中有一个回调方法,当它们完成时会被调用。 我的问题是我怎么知道穿线器抛出的所有线程都结束了?


如果您对已启动的线程数进行计数,则可以在线程启动时简单地增加计数,而在线程完成时则简单地减少计数。

然后,当计数为零时,您就知道所有线程都已完成。

在处理寿命短的线程时,您需要注意确保计数器允许在递减之前递增。需要使用某种锁定或Interlocked.IncrementDecrement来修改计数器变量。资源


您可以为活动线程数保留一个简单的计数器。或者,您可以让线程在开始工作之前在集合中注册(添加)自己,并在完成时将其删除。


计数是可行的,但可能比您想象的要难实现...请确保在线程内的finally块中进行递减并同步(lock)对计数器的访问(我知道,它应该是原子的,但尽管如此...)。

如果仅创建一些线程并拥有线程实例,则也可以使用Thread.Join每个实例。即使线程在调用join之前终止了(但仍在实例被清除之前),该方法仍然有效。

所以:

1
2
3
4
foreach( Thread thread in myThreads)
{
   thread.join();
}

一旦完成,您确定所有线程都已完成。

hth

马里奥


线程计数器的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
long counter = 0;

void ThreadEntryPoint()
{
  try
  {
    ...
    //
  }
  finally
  {
    // Decrement counter
    Interlocked.Decrement(ref counter);
  }
}

void MainThread()
{
  // Start worers
  for(...)
  {
    // Increment threads couter
    Interlocked.Increment(ref counter);
    ((Action)ThreadEntryPoint).BeginInvoke(null, null);
  }

  // Wait until counter is equal to 0
  while(Interlocked.Read(ref counter) != 0)
  {
    Thread.Sleep(0);
  }
}

您可以使用WaitHandle s。如果使用BeginInvoke / EndInvoke来控制线程,则这将变得更加有吸引力(因为IAsyncResult随附了WaitHandle)。只要记住在WaitAll调用期间不要超过64个项目的操作系统限制。这是使过程更简单的扩展方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static class ThreadingExtensions
{
    // Possible:
    // [ThreadStatic]
    // private static List<WaitHandle> PerThreadWaitList;
    public const int MaxHandlesPerWait = 64;

    public static void WaitAll< T >(this IEnumerable< T > handles, int millisecondsTimeout, int estimatedCount)
        where T : WaitHandle
    {
        // Possible:
        // var currentSet = PerThreadWaitList ?? (PerThreadWaitList = new List<WaitHandle>(estimatedCount));
        var currentSet = new List<WaitHandle>(Math.Min(estimatedCount, MaxHandlesPerWait));
        var timeoutEnd = Environment.TickCount + millisecondsTimeout;
        int timeout;

        // Wait for items in groups of 64.
        foreach (var item in handles)
        {
            currentSet.Add(item);
            if (currentSet.Count == MaxHandlesPerWait)
            {
                timeout = Timeout.Infinite;
                if (millisecondsTimeout >= 0)
                {
                    timeout = timeoutEnd - Environment.TickCount;
                    if (timeout < 0)
                        throw new TimeoutException();
                }
                WaitHandle.WaitAll(currentSet.ToArray(), timeout);
                currentSet.Clear();
            }
        }

        // Do the last set.
        if (currentSet.Count > 0)
        {
            timeout = Timeout.Infinite;
            if (millisecondsTimeout >= 0)
            {
                timeout = timeoutEnd - Environment.TickCount;
                if (timeout < 0)
                    timeout = 0;
            }
            WaitHandle.WaitAll(currentSet.ToArray(), timeout);
            currentSet.Clear();
        }
    }
}

并举例说明。

1
2
3
4
var results = new List<IAsyncResult>();
// Call delegates, e.g.
// results.Add(Foo.BeginInvoke(OnEndInvokeFoo));
results.Select(x => x.AsyncWaitHandle).WaitAll(Timeout.Infinite, results.Count);

在穿线器线程中检查每个线程的状态。如果所有线程都已停止(或中止),则可以结束主线程。


最简单的方法是,如果您事先知道将要运行的线程数,则可以在回调函数中使用计数器。

如果您不知道线程数,那么您需要一些"处理结束"指示符来设置布尔值以及已创建和结束线程的计数器。

如果您不知道将要创建的线程数,那么一个简单的两个计数器的想法将行不通(如果第一个线程在创建任何其他线程之前开始并结束,那么它将错误地认为这是最后一个线程)之一)。