[英]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();
}
}
但是 - 你幾乎肯定不想為你描述的場景做這個:
你可以使用: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.Join
, WaitHandle.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.