【问题标题】:C# .Net - How to make application wait until all threads created in Library are finishedC# .Net - 如何让应用程序等到库中创建的所有线程都完成
【发布时间】:2015-11-02 18:22:41
【问题描述】:

我正在尝试创建一个日志库,并且在调用应用程序关闭之前一切都很好。当调用应用程序关闭时,任何未完成的线程都会被杀死并且特定的日志会丢失。

到目前为止,应用程序甚至在前 10 个线程完成之前就退出了。我需要有关如何让应用程序等到库创建的所有线程都完成的帮助。

注意: 我得到的要求是这样的。修改只能在“Logging”类中,因为这将是一个库并将提供给最终用户。必须在应用程序关闭期间处理日志记录问题。这就是我现在遇到问题的地方。

另外一种解决方案,如在日志记录类中创建一个事件以触发所有日志记录完成,并要求用户在该事件上调用应用程序退出是可能的,但我试图避免这种情况,因为它给最终用户增加了负担并增加了复杂性用于实施。他们有可能会跳过它,这是我不想要的。我正在寻找一个解决方案,比如用户应该执行 'Logging.AddException(....)' 然后忘记它。

请帮忙。如果您不清楚这个想法,请提供 cmets。

这是您可以放入控制台应用程序的完整代码摘要。 注意:在 CASE 1 和 CASE 2 中寻找 cmets。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MultithreadKeepAlive
{
class Program
{
    static void Main(string[] args)
    {
        LogLoadTest();
        Logging.AddExceptionEntryAsync(new Exception("Last Exception"));

        /*
         * USE CASE 1: Enable the below lines and you will see how long it is supposed to take.
         * Notice that currentDomain_ProcessExit will not trigger if below gets uncommented
         */
        //Console.WriteLine("Main thread wait override");
        //Console.ReadLine();
    }

    static void LogLoadTest()
    {
        //In real world this will be called from any place of application like startup or just after application shutdown is initiated.
        //: NOTICE: Unlike the sample here, this will never be on loop and I am not looking for handling multithreads in this class.
        //      That responsibility I am planning to assign to Logging class.
        // AND ALSO the class Logging is going to be in a seperate signed assembly where user of this class ('Program') should not worry about multithreads.
        Task t;
        for (int i = 0; i < 40; i++)
        {
           t =  Logging.AddExceptionEntryAsync(new Exception("Hello Exception " + i), "Header info" + i);
        }
    }
}

public class Logging
{
    static List<Task> tasks = new List<Task>();

    static AppDomain currentDomain;
    static Logging()
    {
        currentDomain = AppDomain.CurrentDomain;
        currentDomain.ProcessExit += currentDomain_ProcessExit;
    }

    public static async Task AddExceptionEntryAsync(Exception ex, string header = "")
    {
        Task t = Task.Factory.StartNew(() => AddExceptionEntry(ex, header));
        tasks.Add(t);
        await t;
    }

    public static void AddExceptionEntry(Exception ex, string header)
    {
        /* Exception processing and write to file or DB. This might endup in file locks or 
         * network or any other cases where it will take delays from 1 sec to 5 minutes. */
        Thread.Sleep(new Random().Next(1, 1000));
        Console.WriteLine(ex.Message);
    }

    static void currentDomain_ProcessExit(object sender, EventArgs e)
    {
            Console.WriteLine("Application shutdown triggerd just now.");
            Process.GetCurrentProcess().WaitForExit();    //1st attempt.
            //Task.WaitAll(tasks.ToArray()); //2nd attempt
            while (tasks.Any(t => !t.IsCompleted)) //3rd attempt.
            {
            }
            /* USE CASE 2: IF WORKING GOOD, THIS WILL BE DISPLAYED IN CONSOLE AS LAST 
             * MESSAGE OF APPLICATION AND WILL WAIT FOR USER. THIS IS NOT WORKING NOW.*/
            Console.WriteLine("All complete"); //this message should show up if this work properly
            Console.ReadLine(); //for testing purpose wait for input from user after every thread is complete. Check all 40 threads are in console.
    }
}

}

【问题讨论】:

  • 用线程函数join()试过了吗?如果退出,您可以为每个正在运行的线程调用该方法。所以程序会一直等到它们完成。
  • 为什么这个函数:AddExceptionEntryAsync(Exception ex, string header) 没有标记为async 而只是在里面使用了Task.Delay?您是否阅读过Process.Exit 上的文档?特别是这部分“所有ProcessExit事件处理程序的总执行时间是有限的,就像所有终结器的总执行时间在进程关闭时是有限的。默认为两秒。非托管主机可以通过使用枚举值调用方法来改变这个执行时间。”
  • Ron Beyer, lorenz albert:都试过了。它们适用于应用程序正常运行的正常场景。在这里,当应用程序关闭时,没有任何东西可以阻止它杀死所有线程。我尝试过的线程都会被杀死,并且应用程序在没有写入日志的情况下就存在。
  • 是的,由于我强调的原因,这不会因等待任务完成而改变。延长关闭时间的唯一方法是使用非托管主机来增加执行时间,否则您应该用户决定退出应用程序之前处理此问题。根据您使用的 UI 框架,可能会有要拦截的事件,但不是在控制台应用程序中,当main 停止时应用程序停止,此时应用程序有 2 秒的时间进行清理或运行时将介入为你做。
  • 顺便说一句,我认为您应该多阅读一下async/await 模式,您有一些方法标记为async,但它们返回void 而不是Task (Avoid async void)。您还同步调用async 方法,然后手动生成任务。这里的实现中有很多危险信号,我认为当这些问题得到修复时,很多核心问题都会得到解决。

标签: c# multithreading frameworks shared-libraries application-shutdown


【解决方案1】:

你可以试试

Task.WaitAll(tasks);

这会等待所有提供的 Task 对象完成执行。

更新:使用异步/等待

通过 async 和 await,我们形式化并阐明了异步、非阻塞方法是如何开始和结束的。异步方法只能返回 void 或 Task。

static void Main()
{
// Create task and start it.
// ... Wait for it to complete.
Task task = new Task(AsyncMethod);
task.Start();
task.Wait();
}

public static async void AsyncMethod(){
await AnotherMehod();}

static async Task AnotherMehod() { //TODO}

【讨论】:

  • Rejeb:如果应用程序正常运行,这很有用。我的问题是当应用程序退出时它会被杀死。我想让应用程序保持活动状态和线程运行,直到所有线程都完成。
  • @digitally_inspired 我已经用 async/await 更新了我的答案
【解决方案2】:

到目前为止,我自己找到了解决方法。

    /// <summary>
    /// Makes the current thread Wait until any of the pending messages/Exceptions/Logs are completly written into respective sources.
    /// Call this method before application is shutdown to make sure all logs are saved properly.
    /// </summary>
    public static void WaitForLogComplete()
    {
        Task.WaitAll(tasks.Values.ToArray());
    }

【讨论】:

    【解决方案3】:

    第 1 步:如果您不希望调度程序参与进来,请考虑更改为 Task.Run()。我还假设您想等到所有异步任务完成。

    public static AddExceptionEntry(Exception ex, string header = "")
    {
        Task t = Task.Factory.StartNew(() => AddExceptionEntry(ex, header));
        tasks.Add(t);
    
        WaitForExecutionAsync().ConfigureAwait(true);
    }
    
    public static async Task WaitForExecutionAsync()
    {
        if(tasks.Count >0) 
            await Task.WhenAll(tasks.ToArray());
        // Raise Event.
    }
    

    要阻止只需调用它来运行同步与异步:

    WaitForExecution().GetAwaiter().GetResult();
    

    【讨论】:

    • Step1 我假设这是变化。任务 t = Task.Run(() => AddExceptionEntryAsync(ex, header)); //Task t = Task.Factory.StartNew(() => AddExceptionEntryAsync(ex, header));任务。添加(t);下一个 WaitForExecution().. 你认为这需要在哪里调用?
    • AddExceptionEntry 是否会被调用我的多个线程,这就是保留任务列表并等待执行的原因?
    • 是的,它将随时从应用程序的任何部分调用。并且在应用程序关闭的情况下,它应该能够在不被杀死的情况下完成工作!
    • 因为您使用的是 List 而不是并发集合,如果同时调用 tasks.Add 部分代码,则会出现竞争条件。此外,在不知道您到底想要完成什么的情况下,很难知道最佳解决方案。 AppDomain 类还有一些您应该考虑的其他事件,例如 UnhandledException 和 DomainUnload。
    • 很好的发现.. 谢谢。是的,我试图找到的主要内容是停止应用程序关闭,直到操作完成。我正在以明确的方式执行此操作,用户需要注意调用使用 Task.WaitAll(tasks.ToArray()); 的方法.我想从用户那里移除这个责任,或者确保用户在应用程序启动关闭之前调用它。我有点卡在这里!
    猜你喜欢
    • 2014-10-05
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    • 2016-05-16
    • 2012-07-22
    • 1970-01-01
    • 1970-01-01
    • 2011-05-10
    相关资源
    最近更新 更多