2017-07-04 45 views
3

爲什麼此代碼在Windows和Linux上(使用Mono)的行爲不同?.Net和Mono中的C#Task.WaitAll()

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服務器獲得結果,關鍵是Task.WaitAll()在等待包含調用Dns.GetHostEntry()的任務時如何在Windows和Mono上運行。

在Windows上,如果查詢在超時期限(2s)內沒有返回任何結果,程序將運行或多或少的2秒。也就是說,帶有超時的Task.WaitAll似乎工作。使用Mono在Linux上運行此程序需要2秒鐘才能獲得輸出,但程序不會終止,直到任務退出。這是爲什麼?

似乎我得到相同的執行時間,無論我是否使用Time.WaitAll超時或不。

線索在Dns.GetHostEntry(),因爲Task.WaitAll()按預期工作,如果我使用Thread.Sleep()模擬長時間運行的任務來啓動任務。 可正常工作:

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

是否有辦法在單聲道運行時強制 Task.WaitAll(Task[] tasks, int millisecondsTimeout)實際超時?

編輯:Task.WaitAll()確實在超時時間後返回,但程序在單聲道中運行時不會終止(直到Dns.GetHostEntry超時)。

它不是編譯器。無論使用Visual Studio還是Mono C#編譯器進行編譯,我都可以得到相同的結果。

+0

但是你說'Task.WaitAll'正確超時甚至在單(你在2秒內得到輸出,這意味着任務.WaitAll在2秒內返回)。 – Evk

+0

是的,我的壞。抱歉。該計劃似乎在等待。我將編輯該問題。 –

+0

如果你刪除了'Task.WaitAll'並且在裏面用'Dns.GetHostEntry'開始新任務呢?這也會阻止程序完成嗎? – Evk

回答

3

我會回答我的問題,儘管信貸應該去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是單線程的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查詢?簡單!該功能可以在任務中被調用,它會很樂意聽從了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(); 
}