9

作为 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");
    }
}
4

8 回答 8

12

这样做的方法是保留对所有线程的引用,然后加入它们。这基本上意味着当前线程将阻塞,直到加入的线程完成。

将循环更改为:

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 在等待线程完成时阻塞吗?
于 2012-04-24T08:42:18.833 回答
4

你可以使用:IsAlive。但是你有一个参考,比如

 Thread t = new Thread(new ThreadStart(...));
 t.start();
 if(t.IsAlive)
 {
    //do something
 }
 else
 {
    //do something else
 }
于 2012-04-24T08:48:31.610 回答
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;
}
于 2012-04-24T09:04:29.773 回答
2

永远不要在 GUI 事件处理程序中等待线程完成或其他任何事情。如果您产生许多线程,(是的,不要这样做 - 请参阅 Rob 的帖子),或将许多任务提交到线程池,最后一个完成执行的实体应该向 GUI 线程发出作业完成的信号。通常,这涉及调用某个对象,该对象对剩余的任务/线程进行倒计时,并在最后一个触发时发出信号。查看 System.Threading.CountdownEvent。

于 2012-04-24T09:35:02.573 回答
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();
        }
    }
}
于 2012-04-24T10:16:51.707 回答
2

让我们先解决一些问题。

  • 不要为此创建单独的线程。线程是一种昂贵的资源。而是使用线程池技术。
  • 不要通过调用 , 或任何其他阻塞机制来阻塞 UIThread.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用来让 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"); 
  } 
} 
于 2012-04-24T13:28:36.470 回答
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);

与此相关的具体问题在于 () => 部分。这会产生一个语法错误,显示为Delegate System.Action "System.Threading.Tasks.Task" does not take 0 arguments

我真的希望这能奏效,但我不知道解决方案。我试图查找错误,但我不明白它需要什么参数。如果有人能回答这个问题,那将非常有帮助。这是这个问题唯一缺失的部分。

于 2012-08-02T23:08:57.043 回答
1

您可以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();
}

当所有线程都设置它们的等待句柄时,主线程将继续执行。

于 2012-04-24T08:55:29.950 回答