2016-08-18 107 views
-2

我很難把我的頭圍繞擴展方法。它們是靜態類中的靜態方法。他們如何初始化內部?例如,我寫了下面的擴展方法。線程安全嗎?這個擴展方法是否安全?

public static async Task TimeoutAfter(this Task task, TimeSpan timeSpan) 
{ 
    var cts = new CancellationTokenSource(); 
    try 
    { 
     if (task.IsCompleted || timeSpan == Timeout.InfiniteTimeSpan) 
      return; 
     if (timeSpan == TimeSpan.Zero) 
      throw new TimeoutException(); 
     if (task == await Task.WhenAny(task, Task.Delay(timeSpan, cts.Token))) 
     { 
      cts.Cancel(); 
      await task; 
     } 
     else 
     { 
      throw new TimeoutException(); 
     } 
    } 
    finally 
    { 
     cts.Dispose(); 
    } 
} 
+4

「內部初始化」是什麼意思?方法不是「初始化」的東西。 –

回答

3

所有的擴展方法都被轉

var result = myTask.TimeoutAfter(TimeSpan.FromSecconds(5)); 

var result = ExtensionMethodClass.TimeoutAfter(myTask, TimeSpan.FromSecconds(5)); 

,別無其他。因此,函數是否是擴展方法根本不會影響它的行爲,這只是讓程序員不必從上面的例子中輸入長版本的說服。

至於如果您的代碼是線程安全的,首先您需要了解「線程安全」的含義。我強烈建議您閱讀Eric Lippert撰寫的文章「What is this thing you call "thread safe"?」,它將極大地幫助您瞭解線程安全的含義。

您的代碼不訪問或改變其範圍內的任何外部變量,因此該函數本身是線程安全的,但這並不意味着它不能用於「線程不安全」的方式。幸運的是,您很幸運,TaskTimeSpan都確保您的所有方法和屬性都具有安全性,因此您不太可能遇到任何線程安全問題。


然而所有這一切,你有一個錯誤的競爭條件。如果task.IsCompleted返回true並且task引發異常,您將永遠不會收到該異常的通知。同樣,如果timeSpan == Timeout.InfiniteTimeSpan即使傳入的任務仍在運行,你的函數也會立即返回一個已完成的任務。即使您不打算超時,您也需要await任務。此外,你的try /終於可以simplifed到using聲明

public static async Task TimeoutAfter(this Task task, TimeSpan timeSpan) 
{ 
    using(var cts = new CancellationTokenSource()) 
    { 
     if (task.IsCompleted || timeSpan == Timeout.InfiniteTimeSpan) 
     { 
      await task; 
      return; 
     } 
     if (timeSpan == TimeSpan.Zero) 
      throw new TimeoutException(); 
     if (task == await Task.WhenAny(task, Task.Delay(timeSpan, cts.Token))) 
     { 
      cts.Cancel(); 
      await task; 
     } 
     else 
     { 
      throw new TimeoutException(); 
     } 
    } 
} 

最後,如果你還沒有做它,你將要做出的一個版本,它接受Task<T>太要超時工作情況返回結果。

public static async Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeSpan) 
{ 
    using(var cts = new CancellationTokenSource()) 
    { 
     if (task.IsCompleted || timeSpan == Timeout.InfiniteTimeSpan) 
     { 
      return await task 
     } 
     if (timeSpan == TimeSpan.Zero) 
      throw new TimeoutException(); 
     if (task == await Task.WhenAny(task, Task.Delay(timeSpan, cts.Token))) 
     { 
      cts.Cancel(); 
      return await task; 
     } 
     else 
     { 
      throw new TimeoutException(); 
     } 
    } 
} 
+0

謝謝您的優秀答案以及優化我的代碼! – gebs