2009-12-30 56 views
4

我有一個主線程的應用程序和N工作線程。在某些時候,我需要主線程等待,直到所有的線程完成其工作的一部分。如何停止一個線程,直到n個線程完成其工作

我通常會使用Monitor.Wait()和Monitor.Pulse(),但這會阻止線程同時工作。

任何想法如何做到這一點?

在此先感謝。

+0

只是一個想法:你在下面的評論中描述,你想要表示某個部分已經通過,因爲線程沒有終止。這表示每個線程中有一個循環。您的行爲是否定義爲工作線程在循環中的第二次迭代,因爲它們已經表明它們已完成? – 2009-12-30 16:54:00

+0

@San:不,它不是一個循環,它只是一個很長的操作,爲了避免一些異常,在某個點之前無法停止,所以我需要鎖定Stop方法,直到所有線程都通過該點。 – 2009-12-30 17:02:51

回答

1

好吧,我現在做(使用你的想法),並似乎工作是這樣的:

我宣佈ManualResetEvent的列表:

Private m_waitHandles As List(Of Threading.ManualResetEvent) 

該進程接受傳入的Tcp連接並在每個連接上啓動一個線程。因此,在新的客戶端處理程序我已經添加以下代碼:

Dim waitHandle As Threading.ManualResetEvent 
waitHandle = New Threading.ManualResetEvent(True) 
SyncLock (DirectCast(m_waitHandles, IList).SyncRoot) 
    m_waitHandles.Add(waitHandle) 
End SyncLock 

''# Do all the work 
StoppableMethod() 

SyncLock (DirectCast(m_waitHandles, IList).SyncRoot) 
    waitHandle = m_waitHandles.Item(Threading.WaitHandle.WaitAny(m_waitHandles.ToArray())) 
End SyncLock 

waitHandle.Reset() 
NonStoppableMethod() 
waitHandle.Set() 

SyncLock (DirectCast(m_waitHandles, IList).SyncRoot) 
    m_waitHandles.Remove(waitHandle) 
End SyncLock 

做的最後一件事是修改Stop方法,以確保停止操作不會與NonStoppableMethod內的任何線程中完成的:

SyncLock (DirectCast(m_waitHandles, IList).SyncRoot) 
    If m_waitHandles.Count > 0 Then 
     Threading.WaitHandle.WaitAll(m_waitHandles.ToArray()) 
    End If 
End SyncLock 

我不確定這是否以正確的方式完成,因爲這是我第一次處理這樣的事情。你覺得這是好的,是一個好方法嗎?

感謝所有,配偶!

+1

很高興我們可以幫助! – 2009-12-30 17:37:51

1

如果你只需要等待,直到線程終止,怎麼樣Thread.Join?在.NET 4.0中,您可以使用Task.WaitAll。如果你需要等到他們完成任務的一部分,這是一個小竅門。在當前版本的.NET中,請看WaitHandle.WaitAll/Threading.ManualResetEvent。在.NET 4.0中,您可以使用Threading.Barrier

+0

不,線程不會終止,只是在他們的代碼中重複一點。 – 2009-12-30 16:21:39

+0

@SoMoS:查看MSDN上有關'WaitHandle.WaitAll'和'ManualResetEvent'的示例。 – jason 2009-12-30 17:10:15

2

做一些類似垃圾收集。您將編寫一個ThreadManager,其中有多少個線程正在運行。當主線程啓動一個新工作者時,ThreadManager將增加工作者的數量。當工作完成時,它會通知ThreadManager誰將遞減其線程數。當它有零個工作線程時,ThreadManager將喚醒主線程。

+1

修改或讀取時不要忘記鎖定計數變量。 – 2009-12-30 16:51:13

8

.NET 4.0將包括System.Threading.Barrier類,它將使多個線程之間的同步更容易。有一些很好的示例代碼的博客帖子可以找到here

Similar functionality can be achieved在.NET 3.0+中使用多個WaitHandles,如在MSDN上的this example中所示。

MSDN的例子的小結:

const int numberOfWorkers = 5; 

static void Main() 
{ 
    var handles = new ManualResetEvent[numberOfWorkers]; 

    for (int i = 0; i < numberOfWorkers; i++) 
    { 
     handles[i] = new ManualResetEvent(false); 
     ThreadPool.QueueUserWorkItem(o => worker.Work(), null); 
    } 

    // Wait for all workers to finish before continuing 
    WaitHandle.WaitAll(handles); 
    /* continue execution... */ 
} 
+1

看起來像我需要的,但我現在需要在3.5。有一種方法可以做到這一點? – 2009-12-30 16:22:25

+0

@SoMoS,如果我明白你要做什麼,你可以用'AutoResetEvents'(我在我的答案中描述)使用'WaitAll'來做你想要的。 – 2009-12-30 17:00:24

+0

@Jeff Sternal:我相信我們都提出了相同的解決方案,但有不同的實現。 – 2009-12-30 17:03:12

1

由於在一些實現,有多少把手WaitHandle.WaitAll()可以....處理,(見msdn-WaitHandle.WaitAll()的限制,我創建了一個實用方法爲此:

public static void WaitAll(WaitHandle[] handles) 
    { 
     if (handles == null) 
      throw new ArgumentNullException("handles", 
       "WaitHandle[] handles was null"); 
     foreach (WaitHandle wh in handles) wh.WaitOne(); 
    } 

用法是添加等待句柄每個線程的陣列,然後調用上述工具方法(傳遞數組)的所有線程已經啓動之後

List<WaitHandle> waitHndls = new List<WaitHandle>(); 
foreach (MyType mTyp in MyTypeCollection) 
{ 
    ManualResetEvent txEvnt = new ManualResetEvent(false); 
    int qryNo1 = ++qryNo; 
    ThreadPool.QueueUserWorkItem(
     delegate 
      { 
       try 
       { 
        // Code to execute whatever thread's function is... 
       } 
       catch (SomeCustomException iX) 
       { 
        // catch code 
       }           } 
       finally { lock (locker) txEvnt.Set(); } 
      }); 
    waitHndls.Add(txEvnt); 
} 
util.WaitAll(waitHndls.ToArray()); 
+0

爲什麼不使用'WaitHandle.WaitAll'? – 2009-12-30 16:35:14

+0

因爲WaitAll有多少個句柄是有限制的......沒有雙關語意思)...句柄...從msdn中,「WaitAll方法在所有句柄被髮信號時返回,在一些實現中,如果超過64句柄被傳遞時,拋出NotSupportedException異常。「 – 2009-12-30 16:46:39

+0

確實如此,但'foreach'方法不適用於'AutoResetEvents'(更確切地說,它會產生不可預知的結果,並且可能導致死鎖)。 – 2009-12-30 17:12:15

2

好像WaitHandle.WaitAll應該解決這個問題。

您的主線程將需要保持對工作線程等待句柄的引用。當它需要同步時,將這些句柄傳遞給上述方法。工作線程在其代碼中的適當位置線程發出信號。

如果工作線程循環或需要「脈衝」多次,你可以使用AutoResetEvents,像這樣:

public void WorkerMethod() {   
    DoFirstThing(); 
    this.autoResetEvent.Set(); 
    DoSecondThing(); 
    this.autoResetEvent.Set(); 
    // etc. 
} 

如果沒有(如果只需要在主線程知道工作者線程已經過去了一些門檻),ManualResetEvents會很好。

有幾件事情需要提防使用了WaitAll時(從MSDN WaitAll文檔)的:

在一些實施中,如果超過64 句柄傳遞,一個 NotSupportedException異常被拋出。如果 該陣列包含重複項,則 調用失敗,並返回 DuplicateWaitObjectException。

但是,一個進程真的可以利用超過64個線程的優勢很少,所以這個限制通常並不重要。

+0

我喜歡你的方法。我必須考慮到這是爲了處理傳入的tcp客戶端,所以也許我有更多的64個線程(每個客戶端一個)。我如何檢查這個限制適用於哪裏? – 2009-12-30 17:17:17

+0

不幸的是,這些信息很難得到。我可以在Reflector中看到它被硬編碼到了.NET 2.0實現中,但是手頭沒有3.5程序集。這裏有一篇文章提出了一種巧妙的解決方案來克服極限,這與David Souther在另一個答案中提出的類似:創建自己的'WaitHandle'類來實現引用計數,以便在所有工作線程中共享一個'WaitHandle' 。 http://msdn.microsoft.com/en-us/magazine/cc163914.aspx。 – 2009-12-30 22:07:11

0

使用Thread.Join(即阻塞調用線程,直到某個線程終止,同時繼續執行標準的COM和SendMessage消息泵)像實例方法:

using System; 
using System.Threading; 

class IsThreadPool 
{ 
    static void Main() 
    { 
     AutoResetEvent autoEvent = new AutoResetEvent(false); 

     Thread regularThread = 
      new Thread(new ThreadStart(ThreadMethod)); 
     regularThread.Start(); 
     ThreadPool.QueueUserWorkItem(new WaitCallback(WorkMethod), 
      autoEvent); 

     // __________ Wait for foreground thread to end. __________ 
     regularThread.Join(); 

     // Wait for background thread to end. 
     autoEvent.WaitOne(); 
    } 

    static void ThreadMethod() 
    { 
     Console.WriteLine("ThreadOne, executing ThreadMethod, " + 
      "is {0}from the thread pool.", 
      Thread.CurrentThread.IsThreadPoolThread ? "" : "not "); 
    } 

    static void WorkMethod(object stateInfo) 
    { 
     Console.WriteLine("ThreadTwo, executing WorkMethod, " + 
      "is {0}from the thread pool.", 
      Thread.CurrentThread.IsThreadPoolThread ? "" : "not "); 

     // Signal that this thread is finished. 
     ((AutoResetEvent)stateInfo).Set(); 
    } 
} 
+0

我不等待其他線程終止,只是爲了超過某個點。 – 2009-12-30 16:45:07

+0

@SoMoS:我明白。爲什麼不把它分成兩個線程呢? :)等待第一個,然後離開第二個。 – serhio 2009-12-30 16:59:09

+0

在兩個線程中分割工作看起來不太好,第一個在第二個線程結束時開始第二個。如果我需要檢查2點而不是1點怎麼辦?無論如何謝謝隊友! – 2009-12-30 17:05:19

1

嘗試使用這樣的:

int threadsCompleted = 0; 
int numberOfThreads = 4; 
ManualResetEvent completedEvent = new ManualResetEvent(false); 

在每個線程:

// Do task 

if (Interlocked.Increment(threadsCompleted) == numberOfThreads) 
    completedEvent.Set(); 

主線程:

completedEvent.WaitOne(); 
1

所有在互聯網上的人嘗試使用EventHandlesWaitAll()數組。我想出了以下的課程,這個課程在資源上要輕得多。我試圖想到不同的比賽場景,我相信這段代碼沒有競爭條件。(在遞減和檢查Count上的條件之間存在理論上的競爭,但據我所知它不影響功能,並且代碼將始終工作。)

要使用此類,需要同步的所有線程必須調用其方法Wait()。他們將阻止,直到Count線程數稱爲Wait()。一個實例只能用於同步一次(它不能被重置)。

internal class ThreadBarrier 
{ 
    private ManualResetEvent BarrierEvent; 
    private int Count; 

    internal ThreadBarrier(int count) 
    { 
     BarrierEvent = new ManualResetEvent(false); 
     Count = count; 
    } 

    internal void Wait() 
    { 
     Interlocked.Decrement(ref Count); 
     if (Count > 0) 
      BarrierEvent.WaitOne(); 
     else 
      BarrierEvent.Set(); 
    } 
} 
+0

你是我想出的方法,直到類名。我的不同之處在於我有一個單獨的釋放活動線程的方法,以更好地控制哪些線程塊。 – 2012-12-20 17:17:49

相關問題