C#多线程控制台应用程序-控制台在线程完成之前退出

8
我有一个创建最多5个线程的C#控制台应用程序。
这些线程正在正常执行,但是UI线程在完成其工作后关闭了。
是否有一种方法可以让主UI线程在辅助线程运行时保持运行状态?
foreach (var url in urls)
{
    Console.WriteLine("starting thread: " + url); 
    ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(myMethod), url);
}

我正在按照上面的代码启动我的线程。

如果您创建普通线程而不是使用线程池会怎样呢? - Lasse V. Karlsen
6
使用哪个版本的.NET?如果是.NET 4.0,我建议使用TPL任务,将所有任务存储在数组中,并在退出之前使用Tasks.WaitAll(myTaskArray)。 - James Michael Hare
我认为线程需要设置为 BackgroundWorker=false - IAbstract
@James 应该把那个作为答案发布,顺便说一下博客写得很棒 :) - Porco
6个回答

9

线程池中的线程是后台线程,这意味着退出应用程序不会等待它们完成。

您有几个选项:

  • 使用信号量等待
  • 使用 Sleep() 等待计数器,非常粗糙但对于简单的控制台应用程序来说还可以接受。
  • 使用 TPL,Parallel.ForEach(urls, url => MyMethod(url));

+1,但我不建议使用计数器和睡眠。值得注意的是,可以使用CountDownEvent - Kiril
@Lirik 如果 CountDownEv 是 Fx4+,那么最好使用 TPL。 - H H
@Henk,使用TPL会有一定的开销,如果任务非常短,则TPL效率不高。在这种情况下,最好使用ThreadPool或只有几个专用的消费者线程来处理任务。您可以在两种情况下使用CountDownEvent,或者只需在线程上调用Join... - Kiril
@lirik:OP正在处理URL,我相信使用Task是可行的。 - H H

7
如果您正在使用.NET 4.0:
var tasks = new List<Task>();

foreach(var url in urls)
{
    tasks.Add(Task.Factory.StartNew(myMethod, url));
}

// do other stuff...

// On shutdown, give yourself X number of seconds to wait for them to complete...
Task.WaitAll(tasks.ToArray(), TimeSpan.FromSeconds(30));

2
啊-线程池是后台任务。它被排队,但是你的程序结束了。完成。程序终止。
阅读关于信号量(WaitSignal)和等待的内容-回调中的线程在最后发出信号表明它们已经结束,当所有线程都发出信号时,主线程才能继续。

1
如果您正在使用 .net 4,则:
urls.AsParallel().ForAll(MyMethod);

在 .net 4 之前,可以启动单独的线程,将它们保存在列表中并调用 Join()。工作线程不是后台线程,这会使它们在主线程退出后仍然保持活动状态,但使用 Join() 更加明确。
        List<Thread> workers = new List<Thread>();
        foreach(var url in urls)
        {
            Thread t = new Thread(MyMethod) {IsBackground = false};
            workers.Add(t);
            t.Start(url);
        }

        foreach (var worker in workers)
        {
            worker.Join();
        }

0

解决问题的最简单方法。

在你的程序类中:

static volatile int ThreadsComplete = 0;

在你的“myMethod”方法末尾,在返回之前:
//ThreadsComplete++; //*edit* for safety's sake
Interlocked.Increment(ref ThreadsComplete);

在你的主方法结束之前:
while(ThreadsComplete < urls.Count) { Thread.Sleep(10); }

以上基本上是通过一种 WaitForAll 同步方法进行的黑客攻击。


1
这就是为什么你需要一个CountdownEvent...没有必要使用循环或者睡眠,这种等待方式是不好的实践。 - Kiril
@Henk Holterman和Lirik,你们认为“不良实践”比“错误答案”更主观一些,对吗?这个答案被称为hack,OP也批准了它。所以,我不明白你们想表达什么意思。此外,CountDownEvent是.Net的一个相当新的添加,在这种特定情况下,使用它会比仅使用Parallel ForEach带来更多的编程开销。因此,在这种情况下,CountdownEvent也不是一个“最佳实践”,对吧? - Louis Ricci

-1

在主函数中:

var m = new ManualResetEvent(false);
// do something
foreach (var url in urls)
{
  Console.WriteLine("starting thread: " + url); 
  ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(myMethod), url);
}
m.WaitOne();


private static void myMethod(object obj)
{
  try{
   // do smt
  }
  finally {
    m.Set();
  }
}

如果他把ManualResetEvent改成CountDownEvent,那么这将是一个相当不错的解决方案。 - Kiril
@Lirik 如果进行足够的更改,它也可以写诗。 - L.B
当然,CountDownEvent 是一个真正的东西。永远不要低估它! - brainboost

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接