简体   繁体   English

.Net和Mono中的C#Task.WaitAll()

[英]C# Task.WaitAll() in .Net and Mono

Why does this code behave different on Windows and Linux (using Mono)? 为什么此代码在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 is an example of a query that will eventually time out. 8.8.8.8.dnsrbl.ru是最终超时的查询示例。 I believe there is no working DNS server (or its firewall is blocking me). 我相信没有工作的DNS服务器(或其防火墙阻止我)。

Anyway, the point is not getting a result from the DNS server, the point is how Task.WaitAll() behaves on Windows and Mono when waiting for a task containing a call to Dns.GetHostEntry() . 无论如何,关键是没有从DNS服务器获得结果,关键是Task.WaitAll()在等待包含对Dns.GetHostEntry()的调用的任务时在Windows和Mono上的行为。

On Windows the program takes more or less 2 seconds to run when the query is not returning any result within the timeout period (2s). 在Windows上,当查询未在超时期限(2s)内返回任何结果时,程序运行大约需要2秒钟。 That is, Task.WaitAll with timeout seems to work. 也就是说,具有超时的Task.WaitAll似乎有效。 Running this program on Linux with Mono takes 2 seconds to get the output, but the program does not terminate until the task exits. 使用Mono在Linux上运行此程序需要2秒钟才能获得输出,但程序在任务退出之前不会终止。 Why is that? 这是为什么?

It seems like I get the same execution time regardless if I use Time.WaitAll with timeout or not. 似乎我得到相同的执行时间,无论我是否使用Time.WaitAll超时或不。

The clue is in Dns.GetHostEntry() because Task.WaitAll() works as expected if I start a task with a Thread.Sleep() simulating a long running task. 线索在Dns.GetHostEntry()因为如果我使用模拟长时间运行任务的Thread.Sleep()启动任务, Task.WaitAll()按预期工作。 This works as expected: 这按预期工作:

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

Is there a way to force Task.WaitAll(Task[] tasks, int millisecondsTimeout) to actually time out when running in Mono? 有没有办法强制 Task.WaitAll(Task[] tasks, int millisecondsTimeout)在Mono中运行时实际超时?

Edit: Task.WaitAll() does in fact return after the timeout period, but the program doesn't terminate when running in Mono (until Dns.GetHostEntry times out). 编辑:Task.WaitAll()确实在超时期限后返回,但程序在Mono中运行时不会终止(直到Dns.GetHostEntry超时)。

And it's not the compiler. 而且它不是编译器。 I get the same result whether I compile with Visual Studio or the Mono C# compiler. 无论是使用Visual Studio还是使用Mono C#编译器编译,我都会得到相同的结果。

I'll answer my own question although credit should go to Evk who guided me on the right track (thanks mate!) 我会回答我自己的问题,虽然应该归功于Evk,他引导我走上正轨(感谢队友!)

The subject on this question is bad to say the least. 这个问题的主题至少可以说是坏事。 The problem has nothing to do with Task.WaitAll but rather the Mono implementation of Dns.GetHostEntry . 这个问题有没有关系Task.WaitAll而是单执行Dns.GetHostEntry As Evk said in a comment: 正如Evk在评论中所说:

That means (most likely) that Dns.GetHostEntry on linux starts new non-background thread. 这意味着(最有可能)Linux上的Dns.GetHostEntry启动新的非后台线程。 Program cannot complete until all non-background threads are finished. 在完成所有非后台线程之前,程序无法完成。

The GetHostEntry() method is located in the source file Dns.cs and when called with a string it calls GetHostByName which then calls GetHostByName_internal which is an external C function located in w32socket.c. GetHostEntry()方法位于源文件Dns.cs中,当使用字符串调用时,它调用GetHostByName ,然后调用GetHostByName_internal ,它是位于w32socket.c中的外部C函数。 Finally mono_get_address_info (in networking-posix.c) is called and we are down in the libc function getaddrinfo . 最后mono_get_address_info (在networking-posix.c中),我们在libc函数getaddrinfo Phew! 唷!

I cannot see any new non-background threads being started, but I found this: 我看不到任何新的非后台线程正在启​​动,但我发现了这个:

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

MONO_ENTER_GC_SAFE and MONO_EXIT_GC_SAFE are macros defined in mono-threads-api.h 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)

I did not dig any further, but I believe Evk is right. 我没有进一步挖掘,但我相信Evk是对的。

So, the answer to my question: Dns.GetHostEntry() cannot be terminated or cancelled in Mono. 所以,我的问题的答案: Dns.GetHostEntry()无法在Mono中终止或取消。 A program calling this method will not terminate until all queries has been processed or timed out. 在处理或超时所有查询之前,调用此方法的程序不会终止。 That's the way it is. 它就是这样儿的。 My guess is that is has to do with the garbage collector (GC) which probably runs in non-background thread and hence cannot be cancelled/terminated. 我的猜测是与垃圾收集器(GC)有关,它可能在非后台线程中运行,因此无法取消/终止。

Edit: (A few days later) The reason for this was obvious once I got down to the man-page for getaddrinfo . 编辑:(几天后)一旦我进入getaddrinfo的man-page,这个原因很明显。 This function returns a linked list to the result. 此函数返回结果的链接列表。 This list is of course allocated on the heap, and has to be freed at some point to avoid memory leakage. 此列表当然是在堆上分配的,必须在某些时候释放以避免内存泄漏。 The function for that is freeaddrinfo . 其功能是freeaddrinfo

Anyway, thanks again to Evk! 无论如何,再次感谢Evk!

So how can we fire multiple DNS queries in parallel with a timeout (using Mono)? 那么我们如何在超时的同时触发多个DNS查询(使用Mono)? Easy! 简单! This function can be called in a Task and it will be happy to obey WaitAll with timeout: 可以在任务中调用此函数,并且很乐意在超时时服从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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM