确定所有线程何时完成 c#

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10294469/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 13:13:56  来源:igfitidea点击:

Determining when all threads have finished c#

c#multithreadingthread-safety

提问by Derek

I am very new to threading, as a beginner in C#. I have a program that will be firing multiple threads inside of a windows app. My aim is to start a new thread for every item within a list. The items in this list are workstation names on anetwork. Each thread that is created will look to do repairs on each machine, when the thread has finished it will write to a log file of any errors found etc. But what i want to be able to determine is when all threads have finished. So if i have 100 machines, 100 threads, how do i determine when all have closed?

作为 C# 的初学者,我对线程非常陌生。我有一个程序将在 Windows 应用程序内触发多个线程。我的目标是为列表中的每个项目启动一个新线程。此列表中的项目是网络上的工作站名称。创建的每个线程都将在每台机器上进行修复,当线程完成时,它将写入发现的任何错误等的日志文件。但我希望能够确定的是所有线程何时完成。因此,如果我有 100 台机器、100 个线程,我如何确定所有机器何时关闭?

Heres my method below :-

下面是我的方法:-

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

采纳答案by Rob Levine

The way to do this would be to keep a reference to all the threads and then Joinon them. This basically means that the current thread will block until the joined thread completes.

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

Change your loop to something like:

将您的循环更改为:

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

(where _machineThreads is a list of System.Thread)

(其中 _machineThreads 是一个列表System.Thread

You can then block until all are complete with something like:

然后,您可以阻止,直到所有内容都完成,例如:

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

HOWEVER- you almost certainly don't want to be doing this for the scenario you describe:

但是- 您几乎肯定不想为您描述的场景执行此操作:

  • You shouldn't create a large number of threads - explicitly creating hundreds of threads is not a great idea
  • You should prefer other approaches - try looking at Parallel.ForEachand System.Threading.Task. These days, .Net gives you lots of help when working with threading and asynchronous tasks - I really would advise you read up on it rather than trying to "roll your own" with explicit threads.
  • This looks like a click handler. Is it ASP.NET web forms, or a desktop application? If the former, I certainly wouldn't advise spawning lots of threads to perform background tasks from the request. In either case, do you really want your web page or GUI to block while waiting for the threads to complete?
  • 您不应该创建大量线程 - 显式创建数百个线程并不是一个好主意
  • 您应该更喜欢其他方法 - 尝试查看Parallel.ForEachSystem.Threading.Task。现在,.Net 在处理线程和异步任务时为您提供了很多帮助 - 我真的建议您阅读它,而不是尝试使用显式线程“推出自己的”。
  • 这看起来像一个点击处理程序。它是 ASP.NET Web 表单还是桌面应用程序?如果是前者,我当然不建议从请求中生成大量线程来执行后台任务。在任何一种情况下,您真的希望您的网页或 GUI 在等待线程完成时被阻塞吗?

回答by jorne

you can use: IsAlive. But you have keep a reference like

您可以使用:IsAlive。但你有一个参考

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

回答by Sergey Berezovskiy

You could create WaitHandlefor each thread you are waiting for:

您可以WaitHandle为您正在等待的每个线程创建:

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

Add ManualResetEventto list and pass it to thread:

添加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);

Inside you thread method:

在你的线程方法中:

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

    // do your job 

    waitHandle.Set();
}

Main thread will continue execution when all threads will set their wait handles.

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

回答by weismat

Another idea would be to use Parallel.ForEach in a seperate thread: This is also safe if you have too many machines to fix

另一个想法是在单独的线程中使用 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;
}

回答by Martin James

Never wait for thread completion, or anything else, in a GUI event handler. If you spawn many threads, (and yes, don't do that - see Rob's post), or submit many tasks to a threadpool, the last entitiy to complete execution should signal the GUI thread that the job is complete. Usually, this involves calling into some object that counts down the remaining tasks/threads and signals when the last one fires in. Look at System.Threading.CountdownEvent.

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

回答by Matthew Watson

There is an alternative way of doing this that uses the CountdownEvent class.

有一种使用 CountdownEvent 类的替代方法。

The code that starts the threads must increment a counter, and pass a CountdownEvent object to each thread. Each thread will call CountdownEvent.Signal() when it is finished.

启动线程的代码必须增加一个计数器,并将一个 CountdownEvent 对象传递给每个线程。每个线程在完成时都会调用 CountdownEvent.Signal()。

The following code illustrates this approach:

以下代码说明了这种方法:

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

回答by Brian Gideon

Let us get something out of the way first.

让我们先解决一些问题。

  • Do not create individual threads for this. Threads are an expensive resource. Instead use thread pooling techniques.
  • Do not block the UI thread by calling Thread.Join, WaitHandle.WaitOne, or any other blocking mechanism.
  • 不要为此创建单独的线程。线程是一种昂贵的资源。而是使用线程池技术。
  • 不要通过调用阻塞UI线程Thread.JoinWaitHandle.WaitOne或任何其他阻挡机构。

Here is how I would do this with the TPL.

这是我将如何使用 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"); 
  } 
} 

What I am doing here is creating a parent task that will itself spin up child tasks. Notice that I use TaskCreationOptions.AttachedToParentto associate the child tasks with their parent. Then on the parent task I call ContinueWithwhich gets executed after the parent and all of its children have completed. I use TaskScheduler.FromCurrentSynchronizationContextto get the continuation to happen on the UI thread.

我在这里做的是创建一个父任务,它本身会启动子任务。请注意,我使用TaskCreationOptions.AttachedToParent将子任务与其父任务相关联。然后在我调用的父任务上,它在父任务ContinueWith及其所有子任务完成后执行。我TaskScheduler.FromCurrentSynchronizationContext用来让延续发生在 UI 线程上。

And here is an alternate solution using Parallel.ForEach. Notice that it is a little bit cleaner solution.

这是使用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"); 
  } 
} 

回答by Excuseme baking powder

Brian's solution is not complete and produces a syntax error. If not for the syntax error it would work and solve the initial poster's problem. I do not know how to fix the syntax error thus I'm posting this question for it to be resolved so that the initial question can be resolved. Please Do Not delete this message. it is pertinent to the initial question being answered.

Brian 的解决方案不完整并产生语法错误。如果不是因为语法错误,它会起作用并解决初始海报的问题。我不知道如何修复语法错误,因此我发布了这个问题以供解决,以便解决最初的问题。请不要删除此消息。它与正在回答的最初问题有关。

@Brian Gideon: Your solution would be perfect with the exception of the following code:

@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);

The specific problem with this is in the () => portion. This is producing a syntax error which reads Delegate System.Action "System.Threading.Tasks.Task" does not take 0 arguments

这个的具体问题是在 () => 部分。这会产生一个语法错误,它读取Delegate System.Action "System.Threading.Tasks.Task" 不接受 0 个参数

I really wish this would work and I do not know the solution to this. I've tried to look up the error but I am not understanding what parameters its requiring. If anyone can answer this it would be extremely helpful. This is the only missing piece to this problem.

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