【问题标题】:C# Task.WaitAll() in .Net and Mono.Net 和 Mono 中的 C# Task.WaitAll()
【发布时间】:2017-12-07 14:15:32
【问题描述】:

为什么这段代码在 Windows 和 Linux(使用 Mono)上表现不同?

static void Main(string[] args)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    Task[] tasks = new Task[1];

    tasks[0] = Task.Run(() =>
    {
        IPHostEntry iphe = Dns.GetHostEntry("8.8.8.8.dnsrbl.org");
    });
    Task.WaitAll(tasks, 2000);
    Console.WriteLine("Done in " + stopwatch.ElapsedMilliseconds + " ms");
}

8.8.8.8.dnsrbl.ru 是一个最终会超时的查询示例。我相信没有可用的 DNS 服务器(或者它的防火墙阻止了我)。

无论如何,关键不是从 DNS 服务器获得结果,而是在等待包含对 Dns.GetHostEntry() 的调用的任务时,Task.WaitAll() 在 Windows 和 Mono 上的行为方式。

在 Windows 上,当查询在超时期限 (2 秒) 内没有返回任何结果时,程序运行大约需要 2 秒。也就是说,带有超时的 Task.WaitAll 似乎可以工作。使用 Mono 在 Linux 上运行此程序需要 2 秒才能获得输出,但程序在任务退出之前不会终止。这是为什么呢?

无论我是否使用 Time.WaitAll 超时,我似乎都获得了相同的执行时间。

线索在Dns.GetHostEntry() 中,因为如果我使用Thread.Sleep() 模拟长时间运行的任务,Task.WaitAll() 会按预期工作。 这按预期工作:

tasks[0] = Task.Run(() => Thread.Sleep(10000));

有没有办法强制Task.WaitAll(Task[] tasks, int millisecondsTimeout) 在 Mono 中运行时实际超时?

编辑:Task.WaitAll() 实际上在超时时间后返回,但在 Mono 中运行时程序不会终止(直到 Dns.GetHostEntry 超时)。

而且它不是编译器。无论是使用 Visual Studio 还是 Mono C# 编译器编译,我都会得到相同的结果。

【问题讨论】:

  • 但是你说Task.WaitAll即使在单声道上也会正确超时(你在 2 秒内得到输出,这意味着 Task.WaitAll 在 2 秒内返回)。
  • 是的,我的错。对不起。该程序似乎在等待。我将编辑问题。
  • 如果您删除Task.WaitAll 并在内部使用Dns.GetHostEntry 开始新任务怎么办?这也会阻止程序完成吗?
  • 如果你开始新任务,然后输入Thread.Sleep(2000)并退出呢?
  • 这意味着(很可能)Linux 上的Dns.GetHostEntry 启动了新的非后台线程。在所有非后台线程都完成之前,程序无法完成。确认这一点很难,因为单声道中的Dns.GetHostEntry 最终会调用一些难以分析(甚至找不到)源代码的本机函数。

标签: c# linux multithreading dns mono


【解决方案1】:

我会回答我自己的问题,但应该归功于 Evk,他引导我走上了正确的道路(感谢队友!)

至少可以说这个问题的主题很糟糕。这个问题与Task.WaitAll 无关,而是Dns.GetHostEntry 的Mono 实现。正如 Evk 在评论中所说:

这意味着(很可能)Linux 上的Dns.GetHostEntry 开始新的 非后台线程。程序无法完成,直到所有 非后台线程结束。

GetHostEntry() 方法位于源文件 Dns.cs 中,当使用字符串调用时,它调用 GetHostByName,然后调用 GetHostByName_internal,这是位于 w32socket.c 中的外部 C 函数。最后mono_get_address_info(在networking-posix.c中)被调用,我们在libc函数getaddrinfo中失败了。呸!

我看不到任何新的非后台线程正在启​​动,但我发现了这个:

MONO_ENTER_GC_SAFE;
ret = getaddrinfo (hostname, service_name, &hints, &info);
MONO_EXIT_GC_SAFE;

MONO_ENTER_GC_SAFEMONO_EXIT_GC_SAFE 是在 mono-threads-api.h 中定义的宏

#define MONO_ENTER_GC_SAFE  \
    do {    \
        gpointer __gc_safe_dummy;   \
        gpointer __gc_safe_cookie = mono_threads_enter_gc_safe_region (&__gc_safe_dummy)

#define MONO_EXIT_GC_SAFE   \
        mono_threads_exit_gc_safe_region (__gc_safe_cookie, &__gc_safe_dummy);  \
    } while (0)

我没有进一步挖掘,但我相信 Evk 是对的。

所以,我的问题的答案是:Dns.GetHostEntry() 不能在 Mono 中终止或取消。直到所有查询都已处理或超时,调用此方法的程序才会终止。它就是这样儿的。我的猜测是这与垃圾收集器 (GC) 有关,它可能在非后台线程中运行,因此无法取消/终止。

编辑:(几天后)当我进入getaddrinfo 的手册页后,原因就很明显了。此函数返回结果的链表。这个列表当然是在堆上分配的,并且必须在某个时候释放以避免内存泄漏。其功能是freeaddrinfo

无论如何,再次感谢 Evk!

那么我们如何在超时的情况下并行触发多个 DNS 查询(使用 Mono)?简单!这个函数可以在 Task 中调用,它会很乐意遵守 WaitAll 超时:

private static string host(string query)
{
    ProcessStartInfo psi = new ProcessStartInfo("host", query);
    psi.UseShellExecute = false;
    psi.RedirectStandardOutput = true;
    Process p = Process.Start(psi);
    return p.StandardOutput.ReadToEnd();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-06
    • 1970-01-01
    • 2016-09-12
    相关资源
    最近更新 更多