簡體   English   中英

確定所有線程何時完成c#

[英]Determining when all threads have finished c#

作為C#的初學者,我對線程非常陌生。 我有一個程序將觸發Windows應用程序內的多個線程。 我的目標是為列表中的每個項目啟動一個新線程。 此列表中的項目是網絡上的工作站名稱。 創建的每個線程都會在每台機器上進行修復,當線程完成后,它會寫入任何發現的錯誤的日志文件等。但我想要確定的是所有線程何時完成。 所以,如果我有100台機器,100個線程,我如何確定何時關閉?

以下是我的方法: -

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e)
{
    if (machineList.Count() != 0)
    {
        foreach (string ws in machineList)
        {
            new Thread(new ParameterizedThreadStart(fixClient), stack).Start(ws);
        }
    }
    else
    {
         MessageBox.Show("Please import data before attempting this procedure");
    }
}

這樣做的方法是保持對所有線程的引用,然后加入它們。 這基本上意味着當前線程將阻塞,直到連接的線程完成。

將你的循環改為:

foreach (string ws in machineList)
{
   var thread = new Thread(new ParameterizedThreadStart(fixClient), stack);
   _machineThreads.Add(thread)
   thread.Start();
}

(其中_machineThreads是System.Thread的列表)

然后你可以阻止所有的完成,例如:

private void WaitUntilAllThreadsComplete()
{
   foreach (Thread machineThread in _machineThreads)
   {
      machineThread.Join();
   } 
}

但是 - 你幾乎肯定不想為你描述的場景做這個:

  • 你不應該創建大量的線程 - 顯式創建數百個線程並不是一個好主意
  • 您應該更喜歡其他方法 - 嘗試查看Parallel.ForEachSystem.Threading.Task 現在,.Net在處理線程和異步任務時為您提供了很多幫助 - 我真的建議您閱讀它而不是嘗試使用顯式線程“自己動手”。
  • 這看起來像一個點擊處理程序。 是ASP.NET Web表單還是桌面應用程序? 如果是前者,我當然不會建議產生大量線程來執行請求中的后台任務。 在任何一種情況下,您是否真的希望在等待線程完成時阻止您的網頁或GUI?

你可以使用:IsAlive。 但你有一個參考像

 Thread t = new Thread(new ThreadStart(...));
 t.start();
 if(t.IsAlive)
 {
    //do something
 }
 else
 {
    //do something else
 }

另一個想法是在一個單獨的線程中使用Parallel.ForEach:如果你有太多的機器需要修復,這也是安全的

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e)
{

    if (machineList.Count() != 0)
    {
        AllFinished=False;
        new Thread(new ThreadStart(fixAllClients).Start();
    }
    else
    {
         MessageBox.Show("Please import data before attempting this procedure");
    }
}

private void fixAllClients(){
    var options = new ParallelOptions{MaxDegreeOfParallelism=10};
    Parallel.ForEach(machineList. options, fixClient);
    AllFinished=True;
}

永遠不要等待GUI事件處理程序中的線程完成或其他任何操作。 如果你產生了許多線程,(並且是的,不要這樣做 - 請參閱Rob的帖子),或者向線程池提交許多任務,最后完成執行的權限應該通知GUI線程作業已完成。 通常,這涉及調用某個對象,該對象在最后一個觸發時對剩余的任務/線程和信號進行倒計時。查看System.Threading.CountdownEvent。

有一種替代方法可以使用CountdownEvent類。

啟動線程的代碼必須遞增計數器,並將CountdownEvent對象傳遞給每個線程。 每個線程在完成后都會調用CountdownEvent.Signal()。

以下代碼說明了這種方法:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main(string[] args)
        {
            int numTasks = 20;
            var rng = new Random();

            using (var finishedSignal = new CountdownEvent(1))
            {
                for (int i = 0; i < numTasks; ++i)
                {
                    finishedSignal.AddCount();
                    Task.Factory.StartNew(() => task(rng.Next(2000, 5000), finishedSignal));
                }

                // We started with a count of 1 to prevent a race condition.
                // Now we must decrement that count away by calling .Signal().

                finishedSignal.Signal(); 
                Console.WriteLine("Waiting for all tasks to complete...");
                finishedSignal.Wait();
            }

            Console.WriteLine("Finished waiting for all tasks to complete.");
        }

        static void task(int sleepTime, CountdownEvent finishedSignal)
        {
            Console.WriteLine("Task sleeping for " + sleepTime);
            Thread.Sleep(sleepTime);
            finishedSignal.Signal();
        }
    }
}

讓我們先拿出一些東西。

  • 不要為此創建單獨的線程。 線程是一種昂貴的資源。 而是使用線程池技術。
  • 不要通過調用Thread.JoinWaitHandle.WaitOne或任何其他阻塞機制來阻止UI線程。

以下是我將如何使用TPL執行此操作。

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e) 
{ 
  if (machineList.Count() != 0) 
  { 
    // Start the parent task.
    var task = Task.Factory.StartNew(
      () =>
      {
        foreach (string ws in machineList)
        {
          string capture = ws;
          // Start a new child task and attach it to the parent.
          Task.Factory.StartNew(
            () =>
            {
              fixClient(capture);
            }, TaskCreationOptions.AttachedToParent);
        }
      }, TaskCreationOptions.LongRunning);

    // Define a continuation that happens after everything is done.
    task.ContinueWith(
      (parent) =>
      {
        // Code here will execute after the parent task including its children have finished.
        // You can safely update UI controls here.
      }, TaskScheduler.FromCurrentSynchronizationContext);
  } 
  else 
  { 
    MessageBox.Show("Please import data before attempting this procedure"); 
  } 
} 

我在這里做的是創建一個父任務,它本身會啟動子任務。 請注意,我使用TaskCreationOptions.AttachedToParent將子任務與其父任務相關聯。 然后在父任務上調用ContinueWith ,它在父項及其所有子項完成后執行。 我使用TaskScheduler.FromCurrentSynchronizationContext來繼續在UI線程上發生。

這是使用Parallel.ForEach的替代解決方案。 請注意,這是一個更清潔的解決方案。

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e) 
{ 
  if (machineList.Count() != 0) 
  { 
    // Start the parent task.
    var task = Task.Factory.StartNew(
      () =>
      {
        Parallel.Foreach(machineList,
          ws =>
          {
            fixClient(ws);
          });
      }, TaskCreationOptions.LongRunning);

    // Define a continuation that happens after everything is done.
    task.ContinueWith(
      (parent) =>
      {
        // Code here will execute after the parent task has finished.
        // You can safely update UI controls here.
      }, TaskScheduler.FromCurrentSynchronizationContext);
  } 
  else 
  { 
    MessageBox.Show("Please import data before attempting this procedure"); 
  } 
} 

Brian的解決方案不完整,會產生語法錯誤。 如果不是語法錯誤,它將起作用並解決初始海報的問題。 我不知道如何修復語法錯誤,因此我發布了這個問題,以便解決它,以便解決初始問題。 請不要刪除此消息。 這與回答的最初問題是相關的。

@Brian Gideon:除了以下代碼之外,您的解決方案將是完美的:

// Define a continuation that happens after everything is done.
parent.ContinueWith(
  () =>
  {
    // Code here will execute after the parent task has finished.
    // You can safely update UI controls here.
  }, TaskScheduler.FromCurrentSynchronizationContext);

這個問題的具體問題是在()=>部分。 這產生一個語法錯誤,它讀取Delegate System.Action“System.Threading.Tasks.Task”不帶0個參數

我真的希望這會起作用,我不知道解決方案。 我試圖查找錯誤,但我不明白它需要什么參數。 如果有人能回答這個問題,那將非常有幫助。 這是這個問題唯一缺失的部分。

您可以為等待的每個線程創建WaitHandle

WaitHandle[] waitHandles = new WaitHandle[machineList.Count()];

添加ManualResetEvent以列出並將其傳遞給線程:

for (int i = 0; i < machineList.Count(); i++)
{
    waitHandles[i] = new ManualResetEvent(false);
    object[] parameters = new object[] { machineList[i], waitHandles[i] };
    new Thread(new ParameterizedThreadStart(fixClient), stack).Start(parameters);
}

// wait until each thread will set its manual reset event to signaled state
EventWaitHandle.WaitAll(waitHandles);

你內心的線程方法:

public void fixClient(object state)
{
    object[] parameters = (object[])state;
    string ws = (string)parameters[0];
    EventWaitHandle waitHandle = (EventWaitHandle)parameters[1];

    // do your job 

    waitHandle.Set();
}

當所有線程都設置其等待句柄時,主線程將繼續執行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM