Определить, когда все потоки закончили с#
Я очень новичок в потоковой обработке, как новичок в 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");
}
}
8 ответов
способ сделать это - сохранить ссылку на все потоки, а затем вступить на них. Это в основном означает, что текущий поток будет блокировать до завершения объединенного потока.
измените цикл на что-то вроде:
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. Если вы создаете много потоков (и да, не делайте этого - см. сообщение роба) или отправляете много задач в threadpool, последнее entitiy для завершения выполнения должно сигнализировать потоку GUI, что задание завершено. Обычно это включает вызов некоторого объекта, который отсчитывает оставшиеся задачи / потоки и сигналы, когда последний срабатывает. Посмотрите на систему.Нарезка резьбы.CountdownEvent.
есть альтернативный способ сделать это, который использует класс CountdownEvent.
код, который запускает потоки, должен увеличить счетчик и передать объект CountdownEvent каждому потоку. Каждый поток будет вызывать CountdownEvent.Сигнал (), когда он будет завершен.
следующий код иллюстрирует этот подход:
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();
}
}
}
решение Брайана не завершено и создает синтаксическую ошибку. Если бы не синтаксическая ошибка, это сработало бы и решило проблему исходного плаката. Я не знаю, как исправить синтаксическую ошибку, поэтому я публикую этот вопрос, чтобы он был разрешен, чтобы можно было решить первоначальный вопрос. Пожалуйста, не удаляйте это сообщение. это относится к первоначальному вопросу, на который дается ответ.
@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);
конкретная проблема с этим находится в части ()=>. Это создает синтаксическую ошибку, которая читает Система Делегата.система действий.Нарезка резьбы.Задачи.Задача" не принимает 0 аргументов
Я действительно хочу, чтобы это сработало, и я не знаю решения этого. Я попытался найти ошибку, но я не понимаю, какие параметры ее требуют. Если кто-то может ответить на это было бы крайне полезно. Это единственная недостающая часть эта проблема.
давайте сначала что-нибудь уберем с дороги.
- не создавайте для этого отдельные потоки. Потоки-дорогостоящий ресурс. Вместо этого используйте методы объединения потоков.
- не блокируйте поток пользовательского интерфейса, вызывая
Thread.Join
,WaitHandle.WaitOne
, или любой другой механизм блокирования.
вот как я бы сделал это с 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
чтобы получить продолжение в потоке пользовательского интерфейса.
и вот альтернативное решение с использованием 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");
}
}
вы могли бы создать 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();
}
основной поток продолжит выполнение, когда все потоки будут ждать ручек.