作为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");
}
}
答案 0 :(得分:11)
执行此操作的方法是保留对所有线程的引用,然后Join对它们进行引用。这基本上意味着当前线程将阻塞,直到连接的线程完成。
将循环更改为:
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();
}
}
HOWEVER - 您几乎肯定不希望为您描述的场景执行此操作:
答案 1 :(得分:4)
你可以使用:IsAlive。但你有一个像
这样的参考 Thread t = new Thread(new ThreadStart(...));
t.start();
if(t.IsAlive)
{
//do something
}
else
{
//do something else
}
答案 2 :(得分:2)
另一个想法是在一个单独的线程中使用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;
}
答案 3 :(得分:2)
永远不要等待GUI事件处理程序中的线程完成或其他任何操作。如果你产生了许多线程,(并且是的,不要这样做 - 请参阅Rob的帖子),或者向线程池提交许多任务,最后完成执行的权限应该通知GUI线程作业已完成。通常,这涉及调用某个对象,该对象会在最后一个任务/线程发出信号时倒计时。查看System.Threading.CountdownEvent。
答案 4 :(得分:2)
使用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();
}
}
}
答案 5 :(得分:2)
让我们先取出一些东西。
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");
}
}
答案 6 :(得分:2)
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);
具体问题在于()=&gt;一部分。这产生了一个语法错误,它读取 Delegate System.Action“System.Threading.Tasks.Task”不带0个参数
我真的希望这会起作用,我不知道解决方法。我试图查找错误,但我不明白它需要什么参数。如果有人能回答这个问题,那将非常有帮助。这是这个问题唯一缺失的部分。
答案 7 :(得分:1)
您可以为等待的每个帖子创建WaitHandle
:
WaitHandle[] waitHandles = new WaitHandle[machineList.Count()];
将ManualResetEvent
添加到列表并将其传递给thread:
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();
}
当所有线程都设置等待句柄时,主线程将继续执行。