【问题标题】:Is there a way to globally WaitAll() for all tasks created by a process?有没有办法为进程创建的所有任务全局 WaitAll() ?
【发布时间】:2019-07-30 23:54:50
【问题描述】:

我有一个通过调用外部服务进行日志记录的进程。由于所涉及的开销(很小,但会为许多日志消息建立起来),我的进程以“即发即弃”的方式异步记录。我不想等待每条日志消息都通过后再继续,也不想因为记录器出现问题而使我的流程失败。

为了实现这一点,我将主日志调用封装在一个任务中——每次对日志记录的调用都会触发一个任务,该任务就会停止并完成它的工作。大多数时候,我的进程循环遍历需要检查的内容,处理它们,然后正常退出,一路记录。然而,在它发现只有一个项目要处理的情况下,进程完成得如此之快以至于进程退出,从而在日志记录实际发生之前杀死它的所有线程,而我在日志中几乎没有得到任何信息。

我已经确认这是正在发生的事情,方法是检查项目是否按预期处理,即使它们没有记录(它们是),并在记录方法(在任务),以便日志记录确实阻塞。在这种情况下,一切都按预期记录。

(基于此,我实际上相信即使该过程按预期工作,我们也可能会在每次运行结束时丢失几个日志条目,因为它在最后一个条目可以通过之前退出,但我无法确定。)

我可以在过程的最后放置一个延迟,这样无论如何,它至少会挂起一两秒,让这些“一劳永逸”的任务有时间完成,但这感觉很笨拙。

我正在考虑的另一个选项是创建一个日志记录任务的全局列表,该列表将在创建任务时收集它们,以便我可以对它们执行 Task.WaitAll()。这感觉有点像我不应该处理的开销,但它可能是最好的解决方案。

我正在寻找的是某种方式,在我的流程结束时,执行 WaitAll() 类型的调用,不需要我知道我在等待什么任务 - 只需等待任何和所有任务仍然挂在那里(当然,除了进程的主线程)。

这样的事情是否存在,还是我只需要在全球范围内跟踪我的所有任务?

【问题讨论】:

  • stackoverflow.com/questions/47453915/… 这确实需要您参考您的任务。您可能需要重新考虑您的问题
  • 请注意,“任务”不是“线程”的同义词。虽然可以将任务分配给线程,但它也可以同步运行。
  • 您需要自己跟踪它们。毕竟,您不希望意外等待由不相关的代码创建的真正一劳永逸的任务。例如var discardedTask = Task.Delay(TimeSpan.FromDays(999)); 或者更糟的是,抢别人的任务并重新配置它!
  • @RaymondChen 好吧,从技术上讲,我也想等待由随机代码创建的任务——如果我不这样做,它们将在进程结束时被杀死。毕竟,这仅涵盖在此过程中创建的任务。 “别人的任务”仍然是在这个过程中创建的任务……
  • 追查其他组件创建的任务违反了封装性。在上述情况下,您的应用将在关闭时暂停 999 天。

标签: c# .net multithreading task


【解决方案1】:

您可以创建一个任务聚合器,该任务会在所有观察到的任务完成后完成。它将是Task.WhenAll 的功能等效版本,但更轻量级,因为只存储未完成任务的数量,而不是任务本身。这是这个想法的一个实现:

public class TaskAggregator
{
    private int _activeCount = 0;
    private int _isAddingCompleted = 0;
    private TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>();

    public Task Task { get => _tcs.Task; }

    public int ActiveCount
    {
        get => Interlocked.CompareExchange(ref _activeCount, 0, 0);
    }

    public bool IsAddingCompleted
    {
        get => Interlocked.CompareExchange(ref _isAddingCompleted, 0, 0) != 0;
    }

    public void Add(Task task)
    {
        Interlocked.Increment(ref _activeCount);
        task.ContinueWith(_ =>
        {
            int localActiveCount = Interlocked.Decrement(ref _activeCount);
            if (localActiveCount == 0 && this.IsAddingCompleted)
                _tcs.TrySetResult(true);
        }, TaskContinuationOptions.ExecuteSynchronously);
    }

    public void CompleteAdding()
    {
        Interlocked.Exchange(ref _isAddingCompleted, 1);
        if (this.ActiveCount == 0) _tcs.TrySetResult(true);
    }
}

使用示例:

public static TaskAggregator LogTasksAggregator = new TaskAggregator();

public static void Log(string str)
{
    var logTask = Console.Out.WriteLineAsync(str);
    LogTasksAggregator.Add(logTask);
}

// End of program
LogTasksAggregator.CompleteAdding();
bool completedInTime = LogTasksAggregator.Task.Wait(5000);
if (!completedInTime)
{
    Console.WriteLine("LogTasksAggregator timed out");
}

【讨论】:

  • 因此,没有我想要的选项存在 - 我需要自己跟踪任务并等待它们。也就是说,我喜欢这种方法 - 比维护完整的任务列表并等待所有任务更轻量。
  • .NET Framework 似乎优化得很好,可以像冠军一样处理数百万个任务。例如,在我的机器上等待 50,000,000 个完成的任务需要花费一秒多一点的时间,每个任务消耗的内存不超过 50 字节。但是将程序的每个已完成任务都保存在内存中似乎仍然很浪费!
【解决方案2】:

我本来建议使用TaskScheduler.GetScheduledTasks Method 中记录的internal Task[] System.Threading.Tasks.TaskScheduler.GetScheduledTasksForDebugger(),但它似乎没有返回当前正在运行的任务:

Task.Factory.StartNew(() =>
{
    Console.WriteLine("Sleeping");
    Thread.Sleep(1000);
    Console.WriteLine("Done");
});

// Retrieve method info for internal Task[] GetScheduledTasksForDebugger()
var typeInfo = typeof(System.Threading.Tasks.TaskScheduler);
var bindingAttr = BindingFlags.NonPublic | BindingFlags.Instance;
var methodInfo = typeInfo.GetMethod("GetScheduledTasksForDebugger", bindingAttr);

Task[] tasks = (Task[])methodInfo.Invoke(System.Threading.Tasks.TaskScheduler.Current, null);
Task.WaitAll(tasks);

我认为您将不得不管理 List&lt;Task&gt; 和 WaitAll。

【讨论】:

  • 文档说它返回“当前排队等待执行的调度程序”的任务。仍然未决的任务不是“等待执行”。 “等待执行”表示任务已准备好运行并正在等待轮到它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多